2017 업데이트 : 먼저 오늘 독자들을 위해-Node 7 (4+)에서 작동하는 버전이 있습니다.
function enforceFastProperties(o) {
function Sub() {}
Sub.prototype = o;
var receiver = new Sub(); // create an instance
function ic() { return typeof receiver.foo; } // perform access
ic();
ic();
return o;
eval("o" + o); // ensure no dead code elimination
}
Sans 하나 또는 두 개의 작은 최적화-아래의 모든 내용이 여전히 유효합니다.
먼저 그것이 무엇을하고 왜 더 빠르며 왜 작동하는지에 대해 논의 해 봅시다.
그것이하는 일
V8 엔진은 두 가지 객체 표현을 사용합니다.
- 객체가 키-값 맵으로 해시 맵으로 저장되는 사전 모드 .
- 빠른 모드 -객체가 구조체 처럼 저장 되며 속성 액세스와 관련된 계산은 없습니다.
다음은 속도 차이를 보여주는 간단한 데모 입니다. 여기서는 delete
문장을 사용하여 객체를 느린 사전 모드로 만듭니다.
엔진은 가능할 때마다 그리고 일반적으로 많은 속성 액세스가 수행 될 때마다 빠른 모드를 사용하려고 시도하지만 때로는 사전 모드로 전환됩니다. 사전 모드에 있으면 성능이 크게 저하되므로 일반적으로 객체를 빠른 모드로 설정하는 것이 좋습니다.
이 핵은 개체를 사전 모드에서 빠른 모드로 강제 설정하기위한 것입니다.
왜 더 빠른가
JavaScript 프로토 타입에서 일반적으로 많은 인스턴스간에 공유되는 함수를 저장하고 동적으로 많이 변경하는 경우는 거의 없습니다. 이러한 이유로 함수가 호출 될 때마다 추가 패널티를 피하기 위해 빠른 모드로 설정하는 것이 매우 바람직합니다.
이를 위해 v8은 .prototype
함수의 생성자 인 함수를 호출하여 생성 된 모든 개체가 공유하기 때문에 함수 의 속성 인 개체를 빠른 모드로 설정합니다. 이것은 일반적으로 영리하고 바람직한 최적화입니다.
작동 원리
먼저 코드를 살펴보고 각 줄이 무엇을하는지 알아 봅시다.
function toFastProperties(obj) {
/*jshint -W027*/ // suppress the "unreachable code" error
function f() {} // declare a new function
f.prototype = obj; // assign obj as its prototype to trigger the optimization
// assert the optimization passes to prevent the code from breaking in the
// future in case this optimization breaks:
ASSERT("%HasFastProperties", true, obj); // requires the "native syntax" flag
return f; // return it
eval(obj); // prevent the function from being optimized through dead code
// elimination or further optimizations. This code is never
// reached but even using eval in unreachable code causes v8
// to not optimize functions.
}
우리는하지 않습니다 가 , 우리가 대신 최적화 할 수 않는 V8을 주장 코드를 자신을 찾기 위해 V8 엔진의 단위 테스트를 읽기 :
// Adding this many properties makes it slow.
assertFalse(%HasFastProperties(proto));
DoProtoMagic(proto, set__proto__);
// Making it a prototype makes it fast again.
assertTrue(%HasFastProperties(proto));
이 테스트를 읽고 실행하면이 최적화가 실제로 v8에서 작동 함을 알 수 있습니다. 그러나-방법을 보는 것이 좋을 것입니다.
확인 objects.cc
하면 다음 기능 (L9925)을 찾을 수 있습니다.
void JSObject::OptimizeAsPrototype(Handle<JSObject> object) {
if (object->IsGlobalObject()) return;
// Make sure prototypes are fast objects and their maps have the bit set
// so they remain fast.
if (!object->HasFastProperties()) {
MigrateSlowToFast(object, 0);
}
}
이제 JSObject::MigrateSlowToFast
사전을 명시 적으로 가져 와서 빠른 V8 개체로 변환합니다. v8 객체 내부에 대한 가치있는 읽기와 흥미로운 통찰력이지만 여기서는 주제가 아닙니다. v8 객체에 대해 배우는 좋은 방법이므로 계속 읽으십시오 .
SetPrototype
에서 체크 아웃하면 objects.cc
12231 행에서 호출 된 것을 볼 수 있습니다.
if (value->IsJSObject()) {
JSObject::OptimizeAsPrototype(Handle<JSObject>::cast(value));
}
어느 FuntionSetPrototype
것이 우리가 얻는 것입니다 .prototype =
.
수행 __proto__ =
했거나 .setPrototypeOf
작동했지만 ES6 기능이며 Bluescape는 Netscape 7 이후 모든 브라우저에서 실행되므로 여기서 코드를 단순화하는 것은 의문의 여지가 없습니다. 예를 들어 확인하면 .setPrototypeOf
다음을 볼 수 있습니다.
// ES6 section 19.1.2.19.
function ObjectSetPrototypeOf(obj, proto) {
CHECK_OBJECT_COERCIBLE(obj, "Object.setPrototypeOf");
if (proto !== null && !IS_SPEC_OBJECT(proto)) {
throw MakeTypeError("proto_object_or_null", [proto]);
}
if (IS_SPEC_OBJECT(obj)) {
%SetPrototype(obj, proto); // MAKE IT FAST
}
return obj;
}
어느쪽에 직접 있습니다 Object
:
InstallFunctions($Object, DONT_ENUM, $Array(
...
"setPrototypeOf", ObjectSetPrototypeOf,
...
));
그래서 우리는 Petka가 작성한 코드에서 베어 메탈까지의 길을 걸었습니다. 이것은 좋았다.
부인 성명:
이것은 모든 구현 세부 사항입니다. Petka와 같은 사람들은 최적화 괴물입니다. 조기 최적화는 97 %의 모든 악의 근원이라는 것을 항상 기억하십시오. Bluebird는 매우 기본적인 작업을 매우 자주 수행하므로 이러한 성능 해킹을 통해 많은 이점을 얻을 수 있습니다. 콜백만큼 빠르지는 않습니다. 당신은 거의 도서관에 전원을 공급하지 않는 코드 같은 것을 할 필요가 없습니다.