2011-12-28 13 views
32

Porównując this benchmark chromem 16 vs opera 11.6 stwierdzamy, żeDlaczego Function.prototype.bind jest wolny?

  • w chromowaną rodzimej wiążą jest prawie 5 razy wolniej następnie emulowanego wersji wiążą
  • w operze rodzimy wiążą jest prawie 4 razy szybciej wtedy emulowane wersja wiążą

Jeżeli emulowane wersja wiążą w tym przypadku jest

var emulatebind = function (f, context) { 
    return function() { 
     f.apply(context, arguments); 
    }; 
}; 

Czy istnieją dobre powody, dla których istnieje taka różnica, czy to tylko kwestia V8, która nie jest wystarczająco optymalna?

Uwaga: ten kod emulatebind implementuje tylko podzbiór, ale to nie jest istotne. Jeśli masz w pełni funkcjonalne i zoptymalizowane emulowane wiązanie, nadal istnieje performance difference in the benchmark.

+0

@RobW przedstawił emulowaną wersję binda, którą porównuję. – Raynos

+0

Przypuszczam, że jest to spowodowane inną optymalizacją kodu. Być może otoki z natywnym wiązaniem nie pozwalają na pewne pewne optymalizacje. FF10 demonstruje podobne zachowanie. – kirilloid

+3

Twoja Q. musi być _ "Dlaczego moja emulacja .bind() jest szybsza niż natywna w Chrome, FireFox i wolniejsza w Operze i IE?" _. I dlaczego uważasz, że musi być inaczej? Różna optymalizacja kodu. Emulacja powiązania nie pozwala na dodawanie parametrów, ale tylko kontekst, na przykład. –

Odpowiedz

26

podstawie http://jsperf.com/bind-vs-emulate/6, który dodaje wersję ES5-podkładka dla porównania, wygląda na to winowajcą jest dodatkowy oddział i instanceof że wersja granica musi wykonać, aby sprawdzić, czy to jest nazywane jako konstruktor.

Każdorazowe wersja związana jest prowadzony, kod, który zostanie wykonany jest w istocie:

if (this instanceof bound) { 
    // Never reached, but the `instanceof` check and branch presumably has a cost 
} else { 
    return target.apply(
    that, 
    args.concat(slice.call(arguments)) 
    ); 

    // args is [] in your case. 
    // So the cost is: 
    // * Converting (empty) Arguments object to (empty) array. 
    // * Concating two empty arrays. 
} 

In the V8 source code, kontrola ta wydaje (wewnątrz boundFunction) jako

if (%_IsConstructCall()) { 
    return %NewObjectFromBound(boundFunction); 
} 

(Plaintext link to v8natives.js, gdy Google Code Szukanie umiera.)

To trochę zastanawiające, że w przypadku przeglądarki Chrome 16 wersja es5-shim jest nadal szybsza od wersji na aktualna wersja. I że inne przeglądarki mają raczej różne wyniki dla es5-shim w porównaniu z natywnym. Spekulacja: może %_IsConstructCall() jest nawet wolniejsza niż this instanceof bound, być może ze względu na przekraczanie granic kodu rodzimego/JS. Być może inne przeglądarki mają znacznie szybszy sposób sprawdzania połączenia [[Construct]].

+1

[dodano sprawdzenie instanceof do raynosBound] (http://jsperf.com/bind-vs-emulate/7). Myślę, że narzut koncentruje się głównie na pustych tablicach. Czy uważasz, że warto ręcznie zoptymalizować podkładkę wiążącą ES5 dla przypadku 'args.length === 0', aby zwrócić funkcję, która po prostu robi' cel.apply (to, argumenty) '? – Raynos

+3

Osobiście uważam, że każda optymalizacja to mikrooptymalizacja, a ja zaczekam, aż moje testy wykażą, że zwiazane funkcje wyprzedzają np.opóźnienie sieci powodujące spowolnienie mojej aplikacji przed podjęciem jakichkolwiek decyzji. – Domenic

3

Model V8 source code for bind jest zaimplementowany w JS.

OP nie emuluje bind, ponieważ nie przekierowuje argumentów tak, jak robi to bind. Tutaj jest w pełni funkcjonalnym bind:

var emulatebind = function (f, context) { 
    var curriedArgs = Array.prototype.slice.call(arguments, 2); 
    return function() { 
    var allArgs = curriedArgs.slice(0); 
    for (var i = 0, n = arguments.length; i < n; ++i) { 
     allArgs.push(arguments[i]); 
    } 
    return f.apply(context, allArgs); 
    }; 
}; 

Oczywiście szybkie optymalizacji byłoby zrobić

return f.apply(context, arguments); 

zamiast jeśli curriedArgs.length == 0, ponieważ w przeciwnym razie masz dwie niepotrzebne projekty tablicy i niepotrzebnych kopii, ale może natywna wersja jest naprawdę zaimplementowana w JS i nie wykonuje tej optymalizacji.

Zastrzeżenie: W pełni funkcjonalny bind nie obsługuje poprawnie jakiejś argumentacji corner cases wokół this w trybie ścisłym. To może być kolejne źródło kosztów ogólnych.

+0

Nie emuluje pełnego wiązania. Ale testy wydajności wykorzystują jedynie podzbiór elementów wiązania, które są emulowane. Czy możesz wyjaśnić, dlaczego parametr currying powoduje narzut, gdy nie jest używany w testach wydajności? Czy możesz edytować testy w pełni funkcjonalnym? – Raynos

+0

[Różnica w wydajności nadal istnieje] (http://jsperf.com/bind-vs-emulate/4) – Raynos

+0

@Raynos, dodano link do kodu źródłowego JS dla funkcji wiązania z 'v8natives.js'. –

7

Niemożliwe jest wdrożenie w pełni funkcjonalnego bind tylko w ES5. W szczególności nie można emulować sekcji 15.3.4.5.1 do 15.3.4.5.3 specyfikacji. W szczególności 15.3.4.5.1 wydaje się być możliwym obciążeniem wydajności: w krótkich funkcjach związanych mają różne wewnętrzne właściwości [[Call]], więc wywoływanie ich prawdopodobnie zajmie nietypową i prawdopodobnie bardziej skomplikowaną ścieżkę kodu.

Różne inne specyficzne un-emulatable cechy oprawionego funkcji (takich jak arguments/caller zatrucia oraz ewentualnie zwyczaju length niezależny od oryginalnego podpisu) mogłaby dodać napowietrznych do każdej rozmowy, chociaż muszę przyznać, że to trochę nieprawdopodobne. Chociaż wygląda na to, że V8 nawet nie wprowadza zatrucia w tej chwili.

EDYTUJ tą odpowiedzią jest spekulacja, ale moja druga odpowiedź ma coś bardziej zbliżonego do dowodów. Nadal uważam, że jest to słuszna spekulacja, ale jest to osobna odpowiedź, więc zostawię ją jako taką i po prostu odeślę cię do drugiej.

Powiązane problemy