Dlaczego arr = [] jest szybszy niż arr = new Array?

146

Uruchomiłem ten kod i otrzymałem poniższy wynik. Ciekawi mnie, dlaczego []jest szybszy?

console.time('using[]')
for(var i=0; i<200000; i++){var arr = []};
console.timeEnd('using[]')

console.time('using new')
for(var i=0; i<200000; i++){var arr = new Array};
console.timeEnd('using new')
  • za pomocą []: 299 ms
  • za pomocą new: 363 ms

Dzięki Raynos jest to benchmark tego kodu i bardziej możliwy sposób definiowania zmiennej.

wprowadź opis obrazu tutaj

Mohsen
źródło
5
Możesz być zainteresowany jsperf .
Pointy,
11
Benchmark
Raynos,
Zwróć uwagę na słowo kluczowe new. Oznacza to „proszę być mniej wydajnym”. To nigdy nie ma sensu i wymaga od przeglądarki wykonania normalnej instancji zamiast próby optymalizacji.
beatgammit
2
@kinakuta no. Obie tworzą nowe, nierówne obiekty. Miałem na myśli, że []jest równoważny new Array()pod względem kodu źródłowego, a nie obiektów zwróconych z wyrażeń
Raynos
1
Tak, to nie jest bardzo ważne. Ale lubię wiedzieć.
Mohsen

Odpowiedzi:

195

Dalsze rozszerzenie poprzednich odpowiedzi ...

Z ogólnej perspektywy kompilatorów i pomijając optymalizacje specyficzne dla maszyn wirtualnych:

Najpierw przechodzimy przez fazę analizy leksykalnej, w której tokenizujemy kod.

Przykładowo można wyprodukować następujące tokeny:

[]: ARRAY_INIT
[1]: ARRAY_INIT (NUMBER)
[1, foo]: ARRAY_INIT (NUMBER, IDENTIFIER)
new Array: NEW, IDENTIFIER
new Array(): NEW, IDENTIFIER, CALL
new Array(5): NEW, IDENTIFIER, CALL (NUMBER)
new Array(5,4): NEW, IDENTIFIER, CALL (NUMBER, NUMBER)
new Array(5, foo): NEW, IDENTIFIER, CALL (NUMBER, IDENTIFIER)

Miejmy nadzieję, że powinno to zapewnić wystarczającą wizualizację, abyś mógł zrozumieć, o ile więcej (lub mniej) jest wymagane przetwarzanie.

  1. Na podstawie powyższych tokenów wiemy, że jako fakt ARRAY_INIT zawsze tworzy tablicę. Dlatego po prostu tworzymy tablicę i wypełniamy ją. Jeśli chodzi o niejednoznaczność, na etapie analizy leksykalnej już odróżniono ARRAY_INIT od metody dostępu do właściwości obiektu (np. obj[foo]) Lub nawiasów wewnątrz łańcuchów / literałów wyrażenia regularnego (np. „Foo [] bar” lub / [] /)

  2. To jest malutkie, ale mamy też więcej żetonów z new Array. Co więcej, nie jest jeszcze do końca jasne, że chcemy po prostu utworzyć tablicę. Widzimy „nowy” token, ale „nowy” co? Następnie widzimy token IDENTIFIER, który oznacza, że ​​chcemy nowej „tablicy”, ale maszyny wirtualne JavaScript generalnie nie rozróżniają tokenu IDENTIFIER i tokenów dla „natywnych obiektów globalnych”. W związku z tym...

  3. Za każdym razem, gdy napotkamy token IDENTIFIER, musimy sprawdzić łańcuch zasięgu. Maszyny wirtualne JavaScript zawierają „obiekt aktywacji” dla każdego kontekstu wykonania, który może zawierać obiekt „argumenty”, lokalnie zdefiniowane zmienne itp. Jeśli nie możemy go znaleźć w obiekcie Activation, zaczynamy przeszukiwać łańcuch zasięgu, aż osiągniemy zasięg globalny . Jeśli nic nie zostanie znalezione, rzucamy ReferenceError.

  4. Po zlokalizowaniu deklaracji zmiennej wywołujemy konstruktor. new Arrayjest niejawnym wywołaniem funkcji, a ogólną zasadą jest to, że wywołania funkcji są wolniejsze podczas wykonywania (stąd dlaczego statyczne kompilatory C / C ++ pozwalają na „wstawianie funkcji” - które silniki JS JIT, takie jak SpiderMonkey, muszą wykonywać w locie)

  5. ArrayKonstruktor jest przeciążony. Konstruktor Array jest zaimplementowany jako kod natywny, więc zapewnia pewne ulepszenia wydajności, ale nadal musi sprawdzać długość argumentów i odpowiednio działać. Ponadto, w przypadku podania tylko jednego argumentu, musimy dodatkowo sprawdzić typ argumentu. new Array ("foo") produkuje ["foo"], gdzie jako new Array (1) daje [undefined]

A więc, upraszczając wszystko: dzięki literałom tablicowym maszyna wirtualna wie, że potrzebujemy tablicy; z new Array, VM musi użyć dodatkowych cykli procesora, aby dowiedzieć się, co new Array właściwie robi.

Roger Poon
źródło
nie jest a = new Array (1000); for (od 0 do 999) {a [i] = i} szybciej niż a = []; for (od 0 do 999) {a [i] = i} z powodu choć narzut alokacji?
Y. Yoshii,
Właśnie zrobiłem przypadek testowy. nowy Array (n) jest szybszy w przypadkach, gdy znasz rozmiar tablicy z wyprzedzeniem jsperf.com/square-braces-vs-new-array
Y. Yoshii
27

Jednym z możliwych powodów jest to, że new Arraywymaga on wyszukiwania nazwy Array(możesz mieć zmienną o tej nazwie w zakresie), a []nie.

hammar
źródło
4
Sprawdzanie argumentów też może się przyczynić.
Leonid
Arrayz wyjątkiem jednego argumentu leni wielu argumentów. Gdzie jako []akceptuje tylko wiele argumentów. Również testy Firefoksa nie wykazują prawie żadnej różnicy.
Raynos
Myślę, że jest w tym trochę prawdy. Uruchomienie testu pętli OP w IIFE ma (względnie) istotny wpływ na wydajność. Włączenie var Array = window.Arraypoprawia wydajność new Arraytestu.
user113716
Nie sądzę, żeby to było słuszne, ponieważ to console.time ('więcej vars nowych'); for (var i = 0; i <200000; i ++) {var arr = new Array ()}; console.timeEnd ('więcej vars nowych'); więcej vars nowe: 390ms i ta console.time ('więcej vars nowych'); var myOtherObject = {}, myOtherArray = []; for (var i = 0; i <200000; i ++) {var arr = new Array ()}; console.timeEnd ('więcej vars nowych'); więcej vars nowość: 369ms Powraca w tym samym czasie
Mohsen
2

Dobre pytanie. Pierwszy przykład nazywa się literałem tablicowym. Jest to preferowany sposób tworzenia tablic wśród wielu programistów. Może się zdarzyć, że różnica w wydajności jest spowodowana sprawdzeniem argumentów nowego wywołania Array (), a następnie utworzeniem obiektu, podczas gdy literał tworzy bezpośrednio tablicę.

Myślę, że stosunkowo niewielka różnica w wydajności potwierdza ten punkt. Nawiasem mówiąc, możesz wykonać ten sam test z obiektem i literałem obiektu {}.

Laurent Zuijdwijk
źródło
1

To miałoby jakiś sens

Literały obiektów umożliwiają nam pisanie kodu, który obsługuje wiele funkcji, a jednocześnie sprawia, że ​​jest to stosunkowo proste dla osób wdrażających nasz kod. Nie ma potrzeby bezpośredniego wywoływania konstruktorów ani utrzymywania prawidłowej kolejności argumentów przekazywanych do funkcji itp.

http://www.dyn-web.com/tutorials/obj_lit.php

lnguyen55
źródło
1

Co ciekawe, jeśli długość tablicy jest znana z góry (elementy zostaną dodane zaraz po utworzeniu), użycie konstruktora tablicy o określonej długości jest znacznie szybsze w najnowszym Google Chrome 70+.

  • nowa tablica ( % ARR_LENGTH% ) ” - 100% (szybciej) !

  • [] ” - 160–170% (wolniej)

Wykres z wynikami pomiarów.

Test można znaleźć tutaj - https://jsperf.com/small-arr-init-with-known-length-brackets-vs-new-array/2

Uwaga: ten wynik został przetestowany w przeglądarce Google Chrome v.70 + ; w Firefox v.70 i IE oba warianty są prawie równe.

Oleg Zarevennyi
źródło