바인딩이 클로저보다 느린 이유는 무엇입니까?


79

이전 포스터 에서 Javascript의 Function.bind vs Closure : 선택 방법을 물었습니다 .

그리고 부분적 으로이 답변을 받았습니다. 바인드가 클로저보다 빠르다는 것을 나타냅니다.

범위 순회는 다른 범위에있는 값 (변수, 객체)을 잡기 위해 도달 할 때 추가 오버 헤드가 추가되는 것을 의미합니다 (코드 실행 속도가 느려짐).

bind를 사용하면 기존 범위로 함수를 호출하므로 범위 순회가 발생하지 않습니다.

두 개의 jsperfs는 bind가 실제로 클로저 보다 훨씬 느리다는 것을 암시합니다 .

이것은 위의 코멘트로 게시되었습니다

그리고 저는 제 jsperf 를 작성하기로 결정했습니다.

그렇다면 왜 바인딩이 훨씬 느릴까요 (크롬에서 70 + %)?

더 빠르지 않고 클로저가 동일한 목적을 수행 할 수 있으므로 바인딩을 피해야합니까?


10
"바인딩을 피해야합니다"--- 페이지 당 수천 번 수행하지 않는 한-신경 쓰지 마십시오.
zerkms

1
콜백이 어떻게 든 정렬되어야하기 때문에 작은 조각에서 비동기식 복잡한 작업을 조립하려면 nodejs에서 이와 똑같은 것이 필요할 수 있습니다.
Paul

브라우저가 최적화에 많은 노력을 기울이지 않았기 때문이라고 생각합니다. 수동으로 구현하려면 Mozilla의 코드 ( developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… )를 참조하십시오 . 브라우저가 내부적으로 그렇게 할 수있는 모든 기회가 있으며, 이는 빠른 종료보다 훨씬 더 많은 작업입니다.
Dave

1
간접 함수 호출 ( apply/call/bind)은 일반적으로 직접 호출 보다 훨씬 느립니다.
georg

@zerkms 그리고 누가 수천 번 그렇게하지 않는다고 말할까요? 그것이 제공하는 기능으로 인해 이것이 얼마나 흔한 지 놀랄 것입니다.
Andrew

답변:


142

Chrome 59 업데이트 : 아래 답변에서 예상했듯이 새로운 최적화 컴파일러를 사용하면 bind가 더 이상 느려지지 않습니다. 세부 정보가있는 코드는 다음과 같습니다. https://codereview.chromium.org/2916063002/

대부분의 경우 중요하지 않습니다.

.bind병목 현상이 있는 응용 프로그램을 만드는 경우 가 아니라면 신경 쓰지 않을 것입니다. 가독성은 대부분의 경우 순수한 성능보다 훨씬 더 중요합니다. 네이티브를 사용하면 .bind일반적으로 더 읽기 쉽고 유지 관리가 가능한 코드가 제공 된다고 생각합니다 . 이는 큰 장점입니다.

그러나 예, 중요한 경우- .bind더 느립니다.

예, .bind클로저보다 상당히 느립니다. 적어도 Chrome에서는 v8. 개인적으로 성능 문제 때문에 Node.JS로 전환해야했습니다 (보다 일반적으로 성능 집약적 인 상황에서는 클로저가 다소 느립니다).

왜? 때문에 .bind알고리즘은 더 많은 다른 기능을 갖는 기능을 배치하고 사용하는 것보다 복잡 .call하거나 .apply. (재미있는 사실은 toString이 [native function]으로 설정된 함수도 반환합니다.)

스펙 관점과 구현 관점에서이를 보는 방법에는 두 가지가 있습니다. 둘 다 관찰합시다.

먼저 사양에 정의 된 바인딩 알고리즘을 살펴 보겠습니다 .

  1. Target을 this 값으로 둡니다.
  2. IsCallable (Target)이 false이면 TypeError 예외를 발생시킵니다.
  3. A를 thisArg (arg1, arg2 등) 다음에 제공되는 모든 인수 값의 새로운 (비어있을 수 있음) 내부 목록을 순서대로 지정합니다.

...

(21. 인수 "arguments", PropertyDescriptor {[[Get]] : thrower, [[Set]] : thrower, [[Enumerable]] : false, [[Configurable])를 사용하여 F의 [[DefineOwnProperty]] 내부 메서드 호출) ] : false} 및 false.

(22. 반환 F.

랩보다 훨씬 복잡해 보입니다.

둘째, Chrome에서 어떻게 구현되는지 살펴 보겠습니다 .

FunctionBindv8 (Chrome JavaScript 엔진) 소스 코드를 확인해 보겠습니다 .

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;

여기 구현에서 많은 값 비싼 것들을 볼 수 있습니다. 즉 %_IsConstructCall(). 이것은 물론 사양을 준수하는 데 필요하지만 많은 경우 간단한 랩보다 느리게 만듭니다.


또 다른 메모에서 호출 .bind도 약간 다릅니다. 사양에 "Function.prototype.bind를 사용하여 생성 된 함수 개체에는 프로토 타입 속성이 없거나 [[Code]], [[FormalParameters]] 및 [[Scope]] 내부 속성 "


f = g.bind (stuff); f ()가 g (stuff)보다 느려 야합니까? 나는 이것을 꽤 빨리 찾을 수 있습니다. 그 함수를 인스턴스화 한 것과 상관없이 함수를 호출 할 때마다 동일한 일이 발생하는지 또는 그 함수가 어디에서 왔는지에 따라 달라지는 지 궁금합니다.
Paul

4
@Paul 회의론으로 내 대답을 가져 가십시오. 이 모든 것이 향후 버전의 Chrome (/ V8)에서 최적화 될 수 있습니다. .bind브라우저에서 피하는 경우는 거의 없었 으며 대부분의 경우 읽기 쉽고 이해하기 쉬운 코드가 훨씬 더 중요합니다. 바인딩 된 함수의 속도에 관해서- 예, 바인딩 된 함수는 특히 this부분에서 값이 사용되지 않는 경우 현재 더 느리게 유지 됩니다. 벤치 마크, 사양 및 / 또는 독립적 구현 (벤치 마크)에서이를 확인할 수 있습니다.
Benjamin Gruenbaum 2013

1) 아무것도 (지금 이년이었다) 2) 화살표의 기능은 이후 2013 이후 변경된 : 나는 궁금 디자인에 느린 기능을 화살표입니다 - 어휘 바인딩.
Kuba Wyrostek 2015 년

1
@KubaWyrostek 1) 아니요, 2) 아니요, 바인딩은 설계 상 느리지 않기 때문에 빠르게 구현되지 않았습니다. 화살표 기능은 V8에 아직 착륙하지 않았습니다 (착륙 한 다음 되돌 렸습니다).
Benjamin Gruenbaum 2015-06-28

1
이미 "바인딩"이 적용된 함수에 대한 향후 호출이 더 느려질까요? 즉 a : function () {}. bind (this) ... 처음에 바인딩하지 않은 경우보다 a ()에 대한 향후 호출이 더 느립니까?
wayofthefuture 2015-08-25

1

여기에 약간의 관점을 제공하고 싶습니다.

반면 있습니다 bind()ING가 느린, 전화 결합이 아닌 한 번 기능을!

Linux에서 Firefox 76.0의 내 테스트 코드 :

//Set it up.
q = function(r, s) {

};
r = {};
s = {};
a = [];
for (let n = 0; n < 1000000; ++n) {
  //Tried all 3 of these.
  //a.push(q);
  //a.push(q.bind(r));
  a.push(q.bind(r, s));
}

//Performance-testing.
s = performance.now();
for (let x of a) {
  x();
}
e = performance.now();
document.body.innerHTML = (e - s);

따라서 .bind()ing이 바인딩하지 않는 것보다 약 2 배 더 느릴 수 있다는 것은 사실이지만 (저도 테스트했습니다) 위 코드는 3 가지 경우 (0, 1 또는 2 개의 변수 바인딩)에 대해 동일한 시간이 걸립니다.


개인적으로 .bind()현재 사용 사례에서 ing이 느린 지 여부는 신경 쓰지 않고 해당 변수가 이미 함수에 바인딩되면 호출되는 코드의 성능에 관심이 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.