Wydajność związana z tablicami i obiektami w JavaScript (zwłaszcza Google V8) byłaby bardzo interesująca do udokumentowania. Nigdzie w Internecie nie znalazłem wyczerpującego artykułu na ten temat.
Rozumiem, że niektóre obiekty używają klas jako podstawowej struktury danych. Jeśli właściwości jest dużo, czasami jest to traktowane jako tablica mieszająca?
Rozumiem również, że tablice są czasami traktowane jak tablice C ++ (tj. Szybkie losowe indeksowanie, powolne usuwanie i zmiana rozmiaru). Innym razem są traktowane bardziej jak Obiekty (szybkie indeksowanie, szybkie wstawianie / usuwanie, więcej pamięci). A może czasami są przechowywane jako połączone listy (tj. Powolne losowe indeksowanie, szybkie usuwanie / wstawianie na początku / końcu)
Jaka jest dokładna wydajność pobierania i manipulowania tablicami / obiektami w JavaScript? (specjalnie dla Google V8)
A dokładniej, jaki jest wpływ na wydajność:
- Dodawanie właściwości do obiektu
- 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 Array.pop ()
- Wywołanie Array.push ()
- Wywołanie Array.shift ()
- Wywołanie Array.unshift ()
- Wywołanie Array.slice ()
Wszelkie artykuły lub linki zawierające więcej szczegółów również będą mile widziane. :)
EDYCJA: Naprawdę zastanawiam się, jak tablice i obiekty JavaScript działają pod maską. Ponadto w jakim kontekście silnik V8 „wie” o „przełączeniu” 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?
Albo ... 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...
}
W przypadku konwencjonalnych tablic wydajność byłaby straszna; podczas gdy jeśli użyto LinkedList ... nie jest tak źle.
źródło
Odpowiedzi:
Stworzyłem zestaw testów, właśnie po to, aby zbadać te problemy (i nie tylko) ( kopia archiwalna ).
W tym sensie możesz zobaczyć problemy z wydajnością w tym 50+ testerze przypadków testowych (zajmie to dużo czasu).
Jak sama nazwa wskazuje, bada użycie natywnego charakteru listy połączonej struktury DOM.
(Obecnie niedostępny, odbudowywany w toku) Więcej szczegółów na temat tego na moim blogu .
Podsumowanie jest następujące
Array.shift()
jest szybki ~ ok. 6x wolniejszy niż wyskakująca tablica, ale ok. 100x szybszy niż usunięcie atrybutu obiektu.Array.push( data );
jest szybszy niżArray[nextIndex] = data
prawie 20 (tablica dynamiczna) do 10 (tablica stała) razy.Array.unshift(data)
jest wolniejszy zgodnie z oczekiwaniami i ~ około 5x wolniejszy niż dodawanie nowej właściwości.array[index] = null
jest szybsze niż usunięcie jejdelete array[index]
(niezdefiniowanej) w tablicy o ~ około 4x ++ szybciej.obj[attr] = null
~ około 2x wolniejsze niż zwykłe usunięcie atrybutudelete obj[attr]
Array.splice(index,0,data)
jest wolna, bardzo wolna.Array.splice(index,1,data)
został zoptymalizowany (bez zmiany długości) i jest 100x szybszy niż zwykłe splatanieArray.splice(index,0,data)
dll.splice(index,1)
usuwania (gdzie zepsuł system testowy).Uwaga: te metryki dotyczą tylko dużych tablic / obiektów, które w wersji 8 nie są „całkowicie optymalizowane”. Mogą istnieć bardzo izolowane, zoptymalizowane przypadki wydajności dla rozmiaru tablicy / obiektu mniejszego niż dowolny rozmiar (24?). Więcej szczegółów można znaleźć w kilku filmach Google IO.
Uwaga 2: Te wspaniałe wyniki nie są udostępniane między przeglądarkami, zwłaszcza
*cough*
IE. Również test jest ogromny, dlatego nie udało mi się jeszcze w pełni przeanalizować i ocenić wyników: edytuj go w =)Zaktualizowana uwaga (grudzień 2012 r.): Przedstawiciele Google mają na YouTube filmy opisujące wewnętrzne działanie samego Chrome (np. Gdy przełącza się on z tablicy linkedlist na stałą tablicę itp.) Oraz jak je zoptymalizować. Więcej informacji znajdziesz w GDC 2012: From Console to Chrome .
źródło
Na podstawowym poziomie, który pozostaje w zakresie języka JavaScript, właściwości obiektów są znacznie bardziej złożonymi jednostkami. Możesz tworzyć właściwości za pomocą metod ustawiających / pobierających, z różnymi możliwościami wyliczania, zapisywalności i konfigurowalności. Elementu w tablicy nie można dostosować w ten sposób: albo istnieje, albo nie. Na podstawowym poziomie silnika pozwala to na znacznie większą optymalizację pod względem organizacji pamięci reprezentującej strukturę.
Jeśli chodzi o identyfikację tablicy z obiektu (słownika), silniki JS zawsze tworzyły wyraźne linie między nimi. Dlatego istnieje wiele artykułów na temat metod tworzenia półfałszywego obiektu podobnego do tablicy, który zachowuje się jak jeden, ale umożliwia inne funkcje. Powodem, dla którego ta separacja w ogóle istnieje, jest to, że same silniki JS przechowują je w różny sposób.
Właściwości mogą być przechowywane w obiekcie tablicy, ale to po prostu pokazuje, jak JavaScript nalega na uczynienie wszystkiego obiektem. Indeksowane wartości w tablicy są przechowywane inaczej niż wszelkie właściwości, które zdecydujesz się ustawić w obiekcie tablicy, który reprezentuje podstawowe dane tablicy.
Ilekroć używasz prawidłowego obiektu tablicy i używasz jednej ze standardowych metod manipulowania tą tablicą, będziesz uderzać w podstawowe dane tablicy. W szczególności w wersji 8 są one zasadniczo takie same jak tablice C ++, więc te zasady będą miały zastosowanie. Jeśli z jakiegoś powodu pracujesz z tablicą, której silnik nie jest w stanie określić z pewnością, jest tablicą, to jesteś na znacznie bardziej chwiejnym gruncie. Jednak w ostatnich wersjach V8 jest więcej miejsca do pracy. Na przykład można utworzyć klasę, której prototypem jest Array.prototype, i nadal uzyskać efektywny dostęp do różnych natywnych metod manipulacji tablicami. Ale to ostatnia zmiana.
Przydać się mogą tutaj konkretne linki do ostatnich zmian w manipulowaniu tablicami:
Jako trochę więcej, oto Array Pop i Array Push bezpośrednio ze źródła V8, oba zaimplementowane w samym JS:
źródło
Chciałbym uzupełnić istniejące odpowiedzi dochodzeniem na pytanie, jak zachowują się implementacje w odniesieniu do rosnących tablic: Jeśli zaimplementują je w "zwykły" sposób, można zobaczyć wiele szybkich wypchnięć z rzadkimi, przeplatanymi powolnymi wypychaniami, w którym to momencie implementacja kopiuje wewnętrzna reprezentacja tablicy z jednego bufora do większego.
Bardzo ładnie widać ten efekt, to z Chrome:
Mimo że każde wypchnięcie jest profilowane, dane wyjściowe zawierają tylko te, które wymagają czasu 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.
Czyli pierwsza liczba reprezentuje, który element został wstawiony (pierwsza linia dotyczy 17. elementu), druga to ile czasu to zajęło (dla wielu tablic benchmark jest wykonywany równolegle), a ostatnia wartość to dzielenie elementu pierwsza liczba równa tej z poprzedniego wiersza.
Wszystkie wiersze, których czas wykonania jest krótszy niż 2 ms, są wykluczone z przeglądarki Chrome.
Możesz zobaczyć, że Chrome zwiększa rozmiar tablicy w potęgach 1,5 plus pewne przesunięcie, aby uwzględnić małe tablice.
W przypadku Firefoksa to potęga dwóch:
Musiałem trochę podnieść próg w Firefoksie, dlatego zaczynamy od # 126.
W IE otrzymujemy mieszankę:
Na początku jest to potęga dwóch, a potem przesuwa się do potęgi pięciu trzecich.
Tak więc wszystkie popularne implementacje używają „normalnego” sposobu dla tablic (zamiast na przykład szaleć z linami ).
Oto kod porównawczy, a oto skrzypce , w których się znajduje.
źródło
Podczas pracy pod node.js 0.10 (zbudowany na v8) zauważył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 w tablicy. Więc przeprowadziłem kilka testów.
Ładowanie 91 tys. Wpisów do tablicy (z walidacją i wypychaniem) jest szybsze niż ustawienie obj [klucz] = wartość.
W następnym teście sprawdziłem raz każdą nazwę hosta na liście (91k iteracji, aby uśrednić czas wyszukiwania):
Ta aplikacja to Haraka (serwer SMTP) i ładuje host_list raz podczas uruchamiania (i po zmianach), a następnie wykonuje to wyszukiwanie miliony razy podczas pracy. Przejście na obiekt było ogromną wygraną w wydajności.
źródło