Jaka jest wydajność obiektów/tablic w JavaScript? (specjalnie dla Google V8)

Wydajność związana z tablicami i obiektami w JavaScript (zwłaszcza Google V8) byłaby bardzo interesująca do udokumentowania. Nigdzie w Internecie nie znajduję wyczerpującego artykułu na ten temat.

Rozumiem, że niektóre obiekty używają klas jako podstawowej struktury danych. Jeśli istnieje wiele właściwości, czasami jest traktowana jako tabela hash?

Rozumiem również, że tablice są czasami traktowane jak tablice C++ (tj. szybkie indeksowanie losowe, powolne usuwanie i zmiana rozmiaru). Innym razem są one traktowane bardziej jak obiekty (szybkie indeksowanie, szybkie wstawianie/usuwanie, więcej pamięci). I być może czasami są one przechowywane jako listy połączone (tj. powolne indeksowanie losowe, szybkie usuwanie/Wstawianie na początku/końcu)

Jaka jest dokładna wydajność pobierania tablic / obiektów i manipulacji w JavaScript? (specjalnie dla Google V8)

Dokładniej, jaki jest wpływ wydajności:

  • dodawanie nieruchomości do Obiekt
  • usuwanie właściwości z obiektu
  • indeksowanie właściwości w obiekcie
  • Dodawanie elementu do tablicy
  • usuwanie elementu z tablicy
  • indeksowanie elementu w tablicy
  • Wywołanie Tablicy.pop ()
  • Wywołanie Tablicy.push ()
  • Wywołanie Tablicy.shift ()
  • Wywołanie Tablicy.unshift ()
  • Wywołanie Tablicy.slice ()

Wszelkie artykuły lub linki po więcej szczegółów będą mile widziane, jak również. :)

EDIT: naprawdę zastanawiam się, jak działają tablice i Obiekty JavaScript pod maską. Ponadto, w jakim kontekście czy silnik V8 "wie", aby "przełączyć się" na inną strukturę danych?

Na przykład, załóżmy, że tworzę tablicę z...

var arr = [];
arr[10000000] = 20;
arr.push(21);
Co tu się naprawdę dzieje?

Lub... a co z tym?..???

var arr = [];
//Add lots of items
for(var i = 0; i < 1000000; i++)
    arr[i] = Math.random();
//Now I use it like a queue...
for(var i = 0; i < arr.length; i++)
{
    var item = arr[i].shift();
    //Do something with item...
}

Dla konwencjonalnych tablic wydajność byłaby fatalna; natomiast, gdyby użyto LinkedList... nie tak źle.

Author: BMiner, 2011-12-08

4 answers

UPDATE: zauważ, że JSPref jest obecnie wyłączony

(zapisałem kopię przypadku testowego i zaktualizuję odpowiedź, gdy JSPref zostanie naprawiony / zostanie znaleziony następca)


Hmm... może przesada z odpowiedzią... ale stworzyłem zestaw testów, właśnie po to, aby zgłębić te problemy (i nie tylko) (Kopia zarchiwizowana ).

I w tym sensie, można zobaczyć problemy z wydajnością w tym 50 + test case tester (zajmie to długo).

Również, jak sugeruje jego nazwa, bada wykorzystanie natywnej natury listy połączonej struktury DOM.

(obecnie w dół, przebudowa w toku) więcej szczegółów na moim blogu dotyczące tego .

Podsumowanie jest następujące

  • V8 Array jest szybki, bardzo szybki
  • tablica push / pop / shift jest ~ około 20x + szybsza niż jakikolwiek odpowiednik obiektu.
  • zaskakująco Array.shift() jest szybki ~ok. 6x wolniejszy od tablicy pop, ale jest ~około 100x szybsze niż usuwanie atrybutów obiektu.
  • Array.push( data ); jest szybszy od Array[nextIndex] = data o prawie 20 (tablica dynamiczna) do 10 (tablica stała) razy.
  • Array.unshift(data) jest wolniejszy zgodnie z oczekiwaniami i jest około 5x wolniejszy niż dodawanie nowej właściwości.
  • anulowanie wartości array[index] = null jest szybsze niż usunięcie jej delete array[index] (undefined) w tablicy przez ~około 4x++ szybciej.
  • zaskakująco Zerowanie wartości w obiekcie jest obj[attr] = null ~ok. 2x wolniejsze niż samo usunięcie atrybut delete obj[attr]
  • nic dziwnego, mid array Array.splice(index,0,data) jest powolny, bardzo powolny.
  • zaskakująco, Array.splice(index,1,data) został zoptymalizowany (bez zmiany długości) i jest 100x szybszy niż tylko splice Array.splice(index,0,data)
  • nic dziwnego, że divLinkedList jest gorszy od tablicy we wszystkich sektorach, z wyjątkiem dll.splice(index,1) removal (gdzie złamał system testowy).
  • największa niespodzianka tego wszystkiego [jak zauważył jjrv], zapis tablicy V8 jest nieco szybszy niż odczyt V8 =O

Notatka: te metryki dotyczą tylko dużych tablic/obiektów, których v8 nie "całkowicie optymalizuje". Mogą występować bardzo odizolowane, zoptymalizowane przypadki wydajności dla rozmiaru tablicy/obiektu mniej niż dowolny rozmiar (24?). Więcej szczegółów można zobaczyć w wielu filmach google IO.

Uwaga 2: te wspaniałe wyniki wydajności nie są współdzielone między przeglądarkami, zwłaszcza *cough* IE. Również test jest ogromny, stąd jeszcze w pełni przeanalizować i oceń wyniki: proszę je edytować=)

Zaktualizowana Uwaga (grudzień 2012): przedstawiciele Google mają filmy na youtubach opisujące wewnętrzne działanie samego chrome (np. gdy przełącza się z tablicy linkedlist do tablicy stałej itp.) i jak je zoptymalizować. Zobacz GDC 2012: od konsoli do Chrome Po Więcej.

W 2013 roku, po raz pierwszy w historii, pojawiła się nowa wersja gry, która została wydana w 2013 roku.]}

Aktualizacja Notki (czerwiec 2016): Thx @Benedikt, w odniesieniu do różnicy wydajności wypychania tablic w tablicach stałych / dynamicznych.

 271
Author: PicoCreator,
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
2016-06-04 09:30:15

Na podstawowym poziomie, który pozostaje w sferze JavaScript, właściwości obiektów są znacznie bardziej złożonymi obiektami. Właściwości można tworzyć za pomocą setterów/getterów, różniących się wyliczalnością, pisowalnością i konfigurowalnością. Element w tablicy nie może być dostosowany w ten sposób: albo istnieje, albo nie. na podstawowym poziomie silnika pozwala to na dużo większą optymalizację pod względem organizacji pamięci, która reprezentuje strukturę.

Pod względem identyfikacji tablica z obiektu (słownik), silniki JS zawsze tworzyły wyraźne linie między nimi. Dlatego istnieje wiele artykułów na temat metod tworzenia pół-fałszywego obiektu przypominającego tablicę, który zachowuje się jak jeden, ale pozwala na inne funkcje. Powodem, dla którego ta separacja istnieje, jest to, że same silniki JS przechowują te dwa w różny sposób.

Właściwości mogą być przechowywane na obiekcie array, ale to po prostu pokazuje, jak JavaScript nalega na uczynienie wszystkiego obiektem. Wartości zindeksowane w tablicy są przechowywane inaczej niż jakiekolwiek właściwości, które zdecydujesz się ustawić w obiekcie array, który reprezentuje podstawowe dane tablicy.

Za każdym razem, gdy używasz legit array object I używasz jednej ze standardowych metod manipulowania tą tablicą, uderzasz w dane tablicy. W szczególności w wersji 8 są one zasadniczo takie same jak tablica C++, więc te reguły będą miały zastosowanie. Jeśli z jakiegoś powodu pracujesz z tablicą, której silnik nie jest w stanie określ z pewnością jest tablica, a następnie jesteś na znacznie bardziej wstrząsający Grunt. Z najnowszymi wersjami V8 jest jednak więcej miejsca do pracy. Na przykład, możliwe jest utworzenie klasy, która ma tablicę.prototype jako swój prototype i nadal zyskuje efektywny dostęp do różnych natywnych metod manipulacji tablicami. Ale to niedawna zmiana.

Przydatne mogą być konkretne linki do ostatnich zmian w manipulacji tablicami tutaj:

Jako dodatek, Oto Array Pop i Array Push bezpośrednio ze źródła V8, oba zaimplementowane w samym JS:

function ArrayPop() {
  if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
    throw MakeTypeError("called_on_null_or_undefined",
                        ["Array.prototype.pop"]);
  }

  var n = TO_UINT32(this.length);
  if (n == 0) {
    this.length = n;
    return;
  }
  n--;
  var value = this[n];
  this.length = n;
  delete this[n];
  return value;
}


function ArrayPush() {
  if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
    throw MakeTypeError("called_on_null_or_undefined",
                        ["Array.prototype.push"]);
  }

  var n = TO_UINT32(this.length);
  var m = %_ArgumentsLength();
  for (var i = 0; i < m; i++) {
    this[i+n] = %_Arguments(i);
  }
  this.length = n + m;
  return this.length;
}
 5
Author: Peter O.,
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-12-17 16:03:55

Podczas pracy pod węzłem.js 0.10 (zbudowany na v8) widziałem użycie procesora, które wydawało się nadmierne dla obciążenia. Wyśledziłem jeden problem z wydajnością do funkcji, która sprawdzała istnienie ciągu znaków w tablicy. Więc zrobiłem kilka testów.

  • załadowano 90 822 hostów
  • Ładowanie config trwało 0.087 sekund (array)
  • Ładowanie config trwało 0.152 sekund (obiekt)

Ładowanie 91K wpisów do tablicy (z validate & push) jest szybsze niż ustawienie obj[klucz] = wartość.

W następnym teście, sprawdziłem każdą nazwę hosta na liście jeden raz (91K iteracji, aby uśrednić czas wyszukiwania):

  • wyszukiwanie config trwało 87.56 sekund (array)
  • wyszukiwanie config trwało 0.21 sekund (obiekt)

Tutaj aplikacja to Haraka (serwer SMTP) i ładuje host_list raz przy starcie (i po zmianach), a następnie wykonuje to wyszukiwanie miliony razy podczas operacji. Przejście na obiekt było ogromnym zwycięstwo w wydajności.

 0
Author: Matt Simerson,
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
2014-06-28 19:28:36

Chciałbym uzupełnić istniejące odpowiedzi o badanie na pytanie, jak implementacje zachowują się w odniesieniu do rosnących tablic: jeśli zaimplementują je w "zwykły" sposób, można by zobaczyć wiele szybkich wypychań z rzadkimi, przeplatanymi powolnymi wypychaniami, w którym to momencie implementacja kopiuje wewnętrzną reprezentację tablicy z jednego bufora do większego.

Bardzo ładnie widać ten efekt, to jest z Chrome:

16: 4ms
40: 8ms 2.5
76: 20ms 1.9
130: 31ms 1.7105263157894737
211: 14ms 1.623076923076923
332: 55ms 1.5734597156398105
514: 44ms 1.5481927710843373
787: 61ms 1.5311284046692606
1196: 138ms 1.5196950444726811
1810: 139ms 1.5133779264214047
2731: 299ms 1.5088397790055248
4112: 341ms 1.5056755767118273
6184: 681ms 1.5038910505836576
9292: 1324ms 1.5025873221216042

Mimo że każde naciśnięcie jest profilowane, wyjście zawiera tylko te, które zajmują czas powyżej określonego progu. Dla każdego testu dostosowałem próg, aby wykluczyć wszystkie pchnięcia, które wydają się reprezentować szybkie pchnięcia.

Tak więc pierwsza liczba oznacza, który element został wstawiony (pierwsza linia jest dla 17 elementu), druga to ile czasu zajęło (dla wielu tablic porównywanie jest wykonywane równolegle), a ostatnia wartość jest podziałem pierwszej liczby przez tę w poprzedniej linii.

Wszystkie linie dla Chrome czas wykonania krótszy niż 2 ms jest wykluczony.

Widać, że Chrome zwiększa rozmiar tablicy w potęgach 1.5, plus pewne przesunięcie, aby uwzględnić małe tablice.

Dla Firefoksa to potęga dwóch:

126: 284ms
254: 65ms 2.015873015873016
510: 28ms 2.0078740157480315
1022: 58ms 2.003921568627451
2046: 89ms 2.0019569471624266
4094: 191ms 2.0009775171065494
8190: 364ms 2.0004885197850513

Musiałem trochę podwyższyć próg w Firefoksie, dlatego zaczynamy od #126.

Z IE otrzymujemy mix:

256: 11ms 256
512: 26ms 2
1024: 77ms 2
1708: 113ms 1.66796875
2848: 154ms 1.6674473067915691
4748: 423ms 1.6671348314606742
7916: 944ms 1.6672283066554338

To moc dwóch na początku, a potem przechodzi do mocy pięciu trzecich.

Więc wszystkie wspólne implementacje użyj "normalnego" sposobu dla tablic (zamiast szaleć na przykład linami ).

Oto kod benchmarka ioto skrzypce jest w środku.

var arrayCount = 10000;

var dynamicArrays = [];

for(var j=0;j<arrayCount;j++)
    dynamicArrays[j] = [];

var lastLongI = 1;

for(var i=0;i<10000;i++)
{
    var before = Date.now();
    for(var j=0;j<arrayCount;j++)
        dynamicArrays[j][i] = i;
    var span = Date.now() - before;
    if (span > 10)
    {
      console.log(i + ": " + span + "ms" + " " + (i / lastLongI));
      lastLongI = i;
    }
}
 0
Author: John,
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
2016-09-16 13:54:28