powolne wywoływanie funkcji w wersji V8 przy użyciu tego samego klawisza dla funkcji w różnych obiektach

Może nie dlatego, że wywołanie jest powolne, ale raczej wyszukiwanie jest; nie jestem pewien, ale oto przykład:

var foo = {};
foo.fn = function() {};

var bar = {};
bar.fn = function() {};

console.time('t');

for (var i = 0; i < 100000000; i++) {
    foo.fn();
}

console.timeEnd('t');

Testowane na win8.1

  • firefox 35.01: ~240ms
  • chrome 40.0.2214.93 (V8 3.30.33.15): ~ 760ms
  • msie 11: 34 sec
  • nodejs 0.10.21 (V8 3.14.5.9): ~100ms
  • iojs 1.0.4 (V8 4.1.0.12): ~760ms

Teraz jest interesująca część, jeśli zmienię bar.fn na bar.somethingelse:

  • chrome 40.0.2214.93 (V8 3.30.33.15): ~ 100ms
  • nodejs 0.10.21 (V8 3.14.5.9): ~100ms
  • iojs 1.0.4 (V8 4.1.0.12): ~100ms
Coś poszło nie tak ostatnio w v8? Co jest tego przyczyną?
Author: Totoro, 2015-01-28

2 answers

Pierwsze podstawy.

V8 używa ukrytych klas połączonych z przejściami , aby odkryć statyczną strukturę w puszystych bezkształtnych obiektach JavaScript.

Ukryte klasy opisują strukturę obiektu, przejścia łączą Ukryte klasy ze sobą opisując, która ukryta klasa powinna być użyta, jeśli dana akcja zostanie wykonana na obiekcie.

Na przykład poniższy kod doprowadziłby do następującego łańcucha ukrytych klasy:

var o1 = {};
o1.x = 0;
o1.y = 1;
var o2 = {};
o2.x = 0;
o2.y = 0;

Tutaj wpisz opis obrazka

Ten łańcuch jest tworzony w miarę konstruowania o1. Gdy o2 jest skonstruowany V8 po prostu podąża za ustalonymi przejściami.

Teraz, gdy właściwość fn jest używana do przechowywania funkcji V8 próbuje nadać tej właściwości specjalne traktowanie: zamiast deklarować w ukrytej klasie, że obiekt zawiera właściwość fn V8 umieszcza funkcję w ukrytej klasie.

var o = {};
o.fn = function fff() { };

Tutaj wpisz opis obrazka

Teraz jest ciekawy konsekwencja: jeśli przechowujesz różne funkcje w polu o tej samej nazwie, V8 nie może już po prostu podążać za przejściami, ponieważ wartość właściwości function nie pasuje do wartości oczekiwanej:

var o1 = {};
o1.fn = function fff() { };
var o2 = {};
o2.fn = function ggg() { };

Podczas oceny o2.fn = ... przypisanie V8 zobaczy, że istnieje przejście oznaczone fn, ale prowadzi do ukrytej klasy, która nie nadaje się: zawiera fff W fn właściwości, podczas gdy my próbujemy przechowywać ggg. Uwaga: podałem nazwy funkcji tylko dla uproszczenia - V8 nie używa wewnętrznie swoich nazw, ale ich tożsamości.

Ponieważ V8 nie jest w stanie podążać za tym przejściem, V8 zdecyduje, że jego decyzja o promowaniu funkcji do ukrytej klasy była błędna i marnotrawna. Obraz się zmieni

Tutaj wpisz opis obrazka

V8 utworzy nową ukrytą klasę, gdzie fn jest tylko prostą właściwością, a nie stałą właściwością funkcji. Będzie przekierowywać Przejście, a także oznaczać Stary cel przejścia przestarzały . Pamiętaj, że o1 nadal go używa. Jednak następnym razem, gdy kod dotknie o1, np. gdy właściwość zostanie załadowana z niego - runtime przeniesie o1 z przestarzałej ukrytej klasy. Ma to na celu zmniejszenie polimorfizmu - nie chcemy, aby o1 i o2 miały różne ukryte klasy.

Dlaczego ważne jest posiadanie funkcji na ukrytych klasach? Ponieważ daje to informacje o optymalizującym kompilatorze V8, którego używa do wywołania metody inline. Może tylko wywołać metodę inline, jeśli wywoła cel wywołania jest przechowywany na samej ukrytej klasie.

Teraz zastosujmy tę wiedzę do powyższego przykładu.

Ponieważ istnieje zderzenie między przejściami bar.fn i foo.fn stają się normalnymi właściwościami - Z FUNKCJAMI przechowywanymi bezpośrednio na tych obiektach, a V8 nie może wbudować wywołania foo.fn, co prowadzi do wolniejszej wydajności.

[[29]}czy to możliwe, że wcześniej było połączenie? tak . Oto co się zmieniło: w starszych V8 nie było mechanizmu deprecacji więc nawet po starciu i przekierowany fn transition, {[23] } nie został przeniesiony do ukrytej klasy, gdzie fn staje się normalną właściwością. Zamiast tego {[23] } nadal zachowuje klasę ukrytą, gdzie fn jest stałą właściwością funkcji bezpośrednio wbudowaną w klasę ukrytą, pozwalając kompilatorowi optymalizować jej wbudowanie.

Jeśli spróbujesz timing bar.fn na starszym węźle zobaczysz, że jest wolniejszy:

for (var i = 0; i < 100000000; i++) {
    bar.fn();  // can't inline here
}       

Właśnie dlatego, że używa ukrytej klasy, która nie pozwala na optymalizację kompilatora do inline bar.fn sprawdzam.

Ostatnią rzeczą, jaką należy tutaj zauważyć, jest to, że ten benchmark nie mierzy wydajności wywołania funkcji, ale raczej mierzy, czy optymalizujący kompilator może zredukować tę pętlę do pustej pętli, wprowadzając w nią wywołanie.

 63
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
2015-01-28 21:27:08

Literały obiektów dzielą ukrytą klasę ("map" w terminach wewnętrznych v8) według struktury, tzn. klucze o tej samej nazwie w tej samej kolejności, podczas gdy obiekty utworzone z różnych konstruktorów miałyby różne ukryte klasy, nawet jeśli konstruktory zainicjowały je dokładnie do tych samych pól.

Podczas generowania kodu dla foo.fn(), w kompilatorze zazwyczaj nie masz dostępu do określonego obiektu foo, a tylko do jego ukrytej klasy. Z ukrytej klasy można uzyskać dostęp do funkcji fn, ale ponieważ na współdzielona ukryta klasa może faktycznie mieć inną funkcję przy właściwości fn, co nie jest możliwe. Tak więc, ponieważ nie wiesz w czasie kompilacji, która funkcja zostanie wywołana, nie możesz inline wywołania.

Jeśli uruchomisz kod z flagą trace inlining:

$ /c/etc/iojs.exe --trace-inlining test.js
t: 651ms

Jeśli jednak coś zmienisz tak, że albo .fn jest zawsze tą samą funkcją, albo foo i bar mają inną ukrytą klasę:

$ /c/etc/iojs.exe --trace-inlining test.js
Inlined foo.fn called from .
t: 88ms

(zrobiłem to robiąc bar.asd = 3 before the bar.fn-zadanie, ale istnieje mnóstwo różnych sposobów, aby to osiągnąć, takich jak konstruktory i prototypy, które z pewnością wiesz, że są drogą do wysokiej wydajności javascript)

Aby zobaczyć, co się zmieniło między wersjami, uruchom ten kod:

var foo = {};
foo.fn = function() {};

var bar = {};
bar.fn = function() {};

foo.fn();
console.log("foo and bare share hidden class: ", %HaveSameMap(foo, bar));

Jak widać wyniki różnią się między node10 i iojs:

$ /c/etc/iojs.exe --allow-natives-syntax test.js
foo and bare share hidden class:  true

$ node --allow-natives-syntax test.js
foo and bare share hidden class:  false

Ostatnio nie śledziłem rozwoju v8 w szczegółach, więc nie mogłem wskazać dokładnego powodu, ale tego rodzaju heurystyki zmieniają się cały czas w Generale.

IE11 jest zamkniętym źródłem, ale z wszystkiego, co udokumentowali, wydaje się, że jest bardzo podobny do v8.

 28
Author: Esailija,
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
2015-01-28 20:50:34