Dlaczego new slow?

Benchmark:

JsPerf

Niezmienniki:

var f = function() { };

var g = function() { return this; }

Testy:

Poniżej w kolejności oczekiwanej prędkości

  • new f;
  • g.call(Object.create(Object.prototype));
  • new (function() { })
  • (function() { return this; }).call(Object.create(Object.prototype));

Rzeczywista prędkość:

  1. new f;
  2. g.call(Object.create(Object.prototype));
  3. (function() { return this; }).call(Object.create(Object.prototype));
  4. new (function() { })

Pytanie:

  1. kiedy zamienisz f i g na funkcje anonimowe inline. Dlaczego new (test 4.) testować wolniej?

Update:

Co konkretnie powoduje, że new są wolniejsze, gdy f i g są inlined.

Interesują mnie odniesienia do specyfikacji ES5 lub odniesienia do JagerMonkey lub kodu źródłowego V8. (Możesz też połączyć kod źródłowy JSC i Carakan. Oh I Zespół IE może wyciekać źródło Czakry, jeśli chcą).

Jeśli podlinkujesz jakieś źródło silnika JS, wyjaśnij to.

Author: Raynos, 2011-06-22

5 answers

Problem polega na tym, że możesz sprawdzić aktualny kod źródłowy różnych silników, ale to Ci nie pomoże. Nie próbuj przechytrzyć kompilatora. I tak spróbują zoptymalizować pod kątem najczęstszego użycia. Nie wydaje mi się, aby (function() { return this; }).call(Object.create(Object.prototype)) 1000 razy miał prawdziwy przypadek użycia.

"programy powinny być pisane dla ludzi do czytania, a nawiasem mówiąc tylko dla maszyn do wykonywania."

Abelson & Sussman, SICP, Przedmowa do pierwszego edition

 5
Author: galambalazs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-24 21:02:19

Główna różnica między #4 A wszystkimi innymi przypadkami polega na tym, że pierwszy raz, gdy używasz zamknięcia jako konstruktora, jest zawsze dość drogi.

  1. Jest on zawsze obsługiwany w środowisku uruchomieniowym V8 (Nie w generowanym kodzie), a przejście między skompilowanym kodem JS a środowiskiem uruchomieniowym C++ jest dość kosztowne. Kolejne alokacje są zwykle obsługiwane w wygenerowanym kodzie. Możesz spojrzeć na Generate_JSConstructStubHelper w builtins-ia32.cc i zauważyć, że jest to Runtime_NewObject, gdy zamknięcie nie ma początkowej mapy. (patrz http://code.google.com/p/v8/source/browse/trunk/src/ia32/builtins-ia32.cc#138)

  2. Kiedy closure jest używany jako konstruktor po raz pierwszy, V8 musi utworzyć nową mapę (aka hidden class) I przypisać ją jako initial map dla tego zamknięcia. Zobacz http://code.google.com/p/v8/source/browse/trunk/src/heap.cc#3266 . Ważne jest to, że mapy są przydzielane w oddzielnej przestrzeni pamięci. Ta przestrzeń nie może być czyszczona przez szybkie częściowe scavenge kolekcjoner. W przypadku przepełnienia przestrzeni mapy V8 musi wykonać stosunkowo drogie pełne Mark-sweep GC.

Jest kilka innych rzeczy, które dzieją się, gdy używasz closure jako konstruktora po raz pierwszy, ale 1 i 2 są głównymi przyczynami powolności przypadku testowego #4.

Jeśli porównamy wyrażenia #1 i #4 to różnice są następujące:

  • # 1 nie przydziela za każdym razem nowego zamknięcia;
  • # 1 nie wchodzi za każdym razem w runtime: po closure gets initial map construction jest obsługiwany w szybkiej ścieżce generowanego kodu. Obsługa całej konstrukcji w wygenerowanym kodzie jest znacznie szybsza niż przechodzenie tam iz powrotem między uruchomieniem a wygenerowanym kodem;
  • # 1 nie przydziela nowej mapy początkowej dla każdego nowego zamknięcia za każdym razem;
  • # 1 nie powoduje zamiatania znaczników przez przepełnioną przestrzeń Mapy (tylko tanie zmioty).

Jeśli porównamy #3 i # 4 to różnice są następujące:

  • # 3 nie przydziela nowego pierwsza mapa dla każdego nowego zamknięcia za każdym razem;
  • #3 nie powoduje zamiatania znaczników przez przepełnioną przestrzeń Mapy (tylko tanie zamiatarki);
  • #4 robi mniej po stronie JS (brak funkcji.prototyp.telefon, bez obiektu.create, no Object.prototype lookup etc) więcej Po Stronie C++ (#3 wchodzi również w runtime za każdym razem, gdy robisz obiekt.tworzyć, ale robi tam bardzo mało).

Najważniejsze jest to, że pierwsze użycie closure jako konstruktora jest kosztowne w porównaniu z kolejnymi wywołaniami konstrukcyjnymi z tego samego zamknięcia, ponieważ V8 musi skonfigurować jakąś instalację wodno-kanalizacyjną. Jeśli natychmiast odrzucimy zamknięcie, w zasadzie wyrzucamy całą pracę wykonaną przez V8, aby przyspieszyć kolejne wywołania konstruktora.

 15
Author: Vyacheslav Egorov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-01 09:40:21

Domyślam się, że następujące rozszerzenia wyjaśniają, co się dzieje w V8:

  1. t (exp1): t (Tworzenie obiektu)
  2. t (exp2) : t (Tworzenie obiektu przez obiekt.create ())
  3. t (exp3) : t (Tworzenie obiektu przez obiekt.create ()) + t (Function object Creation)
  4. T (exp4): t ( Tworzenie obiektu) + T(Tworzenie obiektu funkcji) + t (Tworzenie obiektu klasy) [w Chrome]

    • dla ukrytych klas w Chrome spójrz na : http://code.google.com/apis/v8/design.html .
    • gdy nowy obiekt jest tworzony przez obiekt.Utwórz nie musi być tworzony nowy obiekt klasy. Istnieje już taka, która jest używana dla literałów obiektowych i nie ma potrzeby tworzenia nowej klasy.
 3
Author: salman.mirghasemi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-24 14:23:34

Cóż, te dwa telefony nie robią dokładnie tego samego. Rozważ ten przypadek:

var Thing = function () {
  this.hasMass = true;
};
Thing.prototype = { holy: 'object', batman: '!' };
Thing.prototype.constructor = Thing;

var Rock = function () {
  this.hard = 'very';
};
Rock.prototype = new Thing();
Rock.constructor = Rock;

var newRock = new Rock();
var otherRock = Object.create(Object.prototype);
Rock.call(otherRock);

newRock.hard // => 'very'
otherRock.hard // => 'very'
newRock.hasMass // => true
otherRock.hasMass // => undefined
newRock.holy // => 'object'
otherRock.holy // => undefined
newRock instanceof Thing // => true
otherRock instanceof Thing // => false

Widzimy więc, że wywołanie Rock.call(otherRock) nie powoduje dziedziczenia otherRock z prototypu. Musi to uwzględniać przynajmniej niektóre z dodanych powolności. Chociaż w moich testach konstrukcja new jest prawie 30 razy wolniejsza, nawet w tym prostym przykładzie.

 0
Author: benekastah,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-23 15:41:21
new f;
  1. take local function 'f' (access by indeks w ramce lokalnej) - tanio.
  2. execute bytecode BC_NEW_OBJECT (or coś w tym stylu) - tanio.
  3. wykonaj funkcję-tanie tutaj.

Teraz to:

g.call(Object.create(Object.prototype));
  1. Znajdź global var Object - tanio?
  2. Znajdź właściwość prototype w Object-so-so
  3. Znajdź właściwość create W Object-so-so
  4. Find local var g; - cheap
  5. Find property call - so-so
  6. Invoke create function-so-so
  7. Invoke call function-so-so

I to:

new (function() { })
  1. tworzenie nowego obiektu function (tej anonimowej funkcji) - stosunkowo drogie.
  2. execute bytecode BC_NEW_OBJECT - tanie
  3. wykonaj funkcję-tanie tutaj.

Jak widzisz przypadek # 1 jest najmniej zużywający.

 0
Author: c-smile,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-24 21:26:34