Dlaczego bind jest wolniejszy niż zamknięcie?

Poprzedni plakat zadał funkcję.bind vs Closure w Javascript : jak wybrać?

I otrzymałem tę odpowiedź częściowo, co wydaje się wskazywać, że bind powinien być szybszy niż zamknięcie:

Scope traversal oznacza, że gdy sięgasz po wartość (zmienna, obiekt), która istnieje w innym zakresie, dlatego dodawany jest dodatkowy narzut (kod staje się wolniejszy do wykonania).

Używając bind, wywołujesz funkcję z istniejącym zakresem, więc że przemierzanie lunety nie ma miejsca.

Dwa jsperfy sugerują, że bind jest znacznie wolniejszy niż zamknięcie .

To zostało opublikowane jako komentarz do powyższego

I postanowiłem napisać mój własny jsperf

Dlaczego bind jest o wiele wolniejszy (70+% na chromium)?

Ponieważ nie jest to szybsze i zamknięcia mogą służyć temu samemu celowi, należy unikać bind?

Author: Community, 2013-07-14

1 answers

Chrome 59 update: jak przewidywałem w Odpowiedzi poniżej bind nie jest już wolniejszy z nowym kompilatorem optymalizującym. Oto Kod ze szczegółami: https://codereview.chromium.org/2916063002/

Przez większość czasu to nie ma znaczenia.

Chyba, że tworzysz aplikację, w której .bind jest wąskim gardłem, nie zawracałbym sobie głowy. Czytelność jest znacznie ważniejsza niż sama wydajność w większości przypadków. Myślę, że użycie natywnego .bind zazwyczaj zapewnia bardziej czytelne i utrzymywalny Kod - co jest dużym plusem.

Jednak tak, gdy ma to znaczenie- .bind jest wolniejszy

Tak, {[1] } jest znacznie wolniejszy niż zamknięcie-przynajmniej w Chrome, przynajmniej w obecny sposób zaimplementowany w v8. Osobiście musiałem przełączyć się na węzeł.JS dla problemów z wydajnością czasami(bardziej ogólnie, zamknięcia są trochę powolne w sytuacjach intensywnych wydajności).

Dlaczego? Ponieważ algorytm .bind jest o wiele bardziej skomplikowany niż owijanie funkcji z inną funkcją i za pomocą .call lub .apply. (Ciekawostka, zwraca również funkcję z ToString ustawioną na [native function]).

Są dwa sposoby, aby spojrzeć na to z punktu widzenia specyfikacji i z punktu widzenia implementacji. Obserwujmy oba.

Najpierw przyjrzyjmy się algorytmowi bind zdefiniowanemu w specyfikacji :

  1. niech Target będzie tą wartością.
  2. jeśli iscallable (Target) jest false, wyrzuć TypeError wyjątek.
  3. Niech a będzie nową (ewentualnie pustą) wewnętrzną listą wszystkich wartości argumentów podanych po thisArg (arg1, arg2 itd.), w kolejności.

...

(21. Wywołanie wewnętrznej metody f[[DefineOwnProperty]] z argumentami "arguments", PropertyDescriptor {[[Get]]: thrower, [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: false} I false.

(22. Return F.

Wydaje się dość skomplikowane, o wiele więcej niż tylko zawijamy.

Po Drugie, zobaczmy Jak to jest zaimplementowane w Chrome .

Sprawdźmy FunctionBind w kodzie źródłowym V8 (chrome JavaScript engine):

function FunctionBind(this_arg) { // Length is 1.
  if (!IS_SPEC_FUNCTION(this)) {
    throw new $TypeError('Bind must be called on a function');
  }
  var boundFunction = function () {
    // Poison .arguments and .caller, but is otherwise not detectable.
    "use strict";
    // This function must not use any object literals (Object, Array, RegExp),
    // since the literals-array is being used to store the bound data.
    if (%_IsConstructCall()) {
      return %NewObjectFromBound(boundFunction);
    }
    var bindings = %BoundFunctionGetBindings(boundFunction);

    var argc = %_ArgumentsLength();
    if (argc == 0) {
      return %Apply(bindings[0], bindings[1], bindings, 2, bindings.length - 2);
    }
    if (bindings.length === 2) {
      return %Apply(bindings[0], bindings[1], arguments, 0, argc);
    }
    var bound_argc = bindings.length - 2;
    var argv = new InternalArray(bound_argc + argc);
    for (var i = 0; i < bound_argc; i++) {
      argv[i] = bindings[i + 2];
    }
    for (var j = 0; j < argc; j++) {
      argv[i++] = %_Arguments(j);
    }
    return %Apply(bindings[0], bindings[1], argv, 0, bound_argc + argc);
  };

  %FunctionRemovePrototype(boundFunction);
  var new_length = 0;
  if (%_ClassOf(this) == "Function") {
    // Function or FunctionProxy.
    var old_length = this.length;
    // FunctionProxies might provide a non-UInt32 value. If so, ignore it.
    if ((typeof old_length === "number") &&
        ((old_length >>> 0) === old_length)) {
      var argc = %_ArgumentsLength();
      if (argc > 0) argc--;  // Don't count the thisArg as parameter.
      new_length = old_length - argc;
      if (new_length < 0) new_length = 0;
    }
  }
  // This runtime function finds any remaining arguments on the stack,
  // so we don't pass the arguments object.
  var result = %FunctionBindArguments(boundFunction, this,
                                      this_arg, new_length);

  // We already have caller and arguments properties on functions,
  // which are non-configurable. It therefore makes no sence to
  // try to redefine these as defined by the spec. The spec says
  // that bind should make these throw a TypeError if get or set
  // is called and make them non-enumerable and non-configurable.
  // To be consistent with our normal functions we leave this as it is.
  // TODO(lrn): Do set these to be thrower.
  return result;

Widzimy tu masę drogich rzeczy w realizacji. Mianowicie %_IsConstructCall(). Jest to oczywiście potrzebne do przestrzegania specyfikacji - ale w wielu przypadkach sprawia, że jest to wolniejsze niż zwykłe zawijanie.


Z innej uwagi, wywołanie .bind jest również nieco inne, uwagi spec "Obiekty funkcyjne utworzone przy użyciu funkcji.prototyp.bind nie posiada właściwości prototype ani właściwości [[Code]], [[FormalParameters]] oraz [[Scope]] wewnętrzne"

 129
Author: Benjamin Gruenbaum,
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
2017-10-18 10:18:34