Development Tip

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

yourdevel 2020. 10. 16. 08:10
반응형

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


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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

  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;

We can see a bunch of expensive things here in the implementation. Namely %_IsConstructCall(). This is of course needed to abide to the specification - but it also makes it slower than a simple wrap in many cases.


On another note, calling .bind is also slightly different, the spec notes "Function objects created using Function.prototype.bind do not have a prototype property or the [[Code]], [[FormalParameters]], and [[Scope]] internal properties"

참고URL : https://stackoverflow.com/questions/17638305/why-is-bind-slower-than-a-closure

반응형