[javascript] 바인딩이 클로저보다 느린 이유는 무엇입니까?
이전 포스터 에서 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]으로 설정된 함수도 반환합니다.)
스펙 관점과 구현 관점에서이를 보는 방법에는 두 가지가 있습니다. 둘 다 관찰합시다.
먼저 사양에 정의 된 바인딩 알고리즘을 살펴 보겠습니다 .
- Target을 this 값으로 둡니다.
- IsCallable (Target)이 false이면 TypeError 예외를 발생시킵니다.
- A를 thisArg (arg1, arg2 등) 다음에 제공되는 모든 인수 값의 새로운 (비어있을 수 있음) 내부 목록을 순서대로 지정합니다.
…
(21. 인수 “arguments”, PropertyDescriptor {[[Get]] : thrower, [[Set]] : thrower, [[Enumerable]] : false, [[Configurable])를 사용하여 F의 [[DefineOwnProperty]] 내부 메서드 호출) ] : false} 및 false.
(22. 반환 F.
랩보다 훨씬 복잡해 보입니다.
둘째, Chrome에서 어떻게 구현되는지 살펴 보겠습니다 .
FunctionBind
v8 (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]] 내부 속성 “
답변
여기에 약간의 관점을 제공하고 싶습니다.
반면 있습니다 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이 느린 지 여부는 신경 쓰지 않고 해당 변수가 이미 함수에 바인딩되면 호출되는 코드의 성능에 관심이 있습니다.