Bluebird의 util.toFastProperties 함수는 어떻게 객체의 속성을 "빠르게"만드는가?


165

Bluebird의 util.js파일 에는 다음과 같은 기능이 있습니다.

function toFastProperties(obj) {
    /*jshint -W027*/
    function f() {}
    f.prototype = obj;
    ASSERT("%HasFastProperties", true, obj);
    return f;
    eval(obj);
}

어떤 이유로 반환 함수 뒤에 문이 있는데 왜 거기에 있는지 잘 모르겠습니다.

또한 저자가 이것에 대해 JSHint 경고를 침묵 시켰기 때문에 의도적 인 것으로 보입니다.

'반품'후 도달 할 수없는 '평가'. (W027)

이 기능은 정확히 무엇을합니까? 않습니다 util.toFastProperties정말 "빠른"객체의 속성을?

Bluebird의 GitHub 리포지토리를 통해 소스 코드에 대한 의견이나 문제 목록에 대한 설명을 찾았지만 찾을 수 없었습니다.

답변:


314

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.cc12231 행에서 호출 된 것을 볼 수 있습니다.

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는 매우 기본적인 작업을 매우 자주 수행하므로 이러한 성능 해킹을 통해 많은 이점을 얻을 수 있습니다. 콜백만큼 빠르지는 않습니다. 당신은 거의 도서관에 전원을 공급하지 않는 코드 같은 것을 할 필요가 없습니다.


37
이것은 내가 읽은 가장 흥미로운 게시물입니다. 당신에게 많은 존경과 감사!
m59

2
@timoxley 필자는 eval코드 OP 게시를 설명 할 때 코드 주석에서 다음에 대해 다음과 같이 썼다 . 기능. " . 다음은 관련 읽기 입니다. 이 주제에 대해 더 자세히 설명해 주시겠습니까?
Benjamin Gruenbaum

3
@dherman a 1;는 "최적화 해제"를 일으키지 debugger;않았을 것입니다. 아마 똑같이 잘 작동했을 것입니다. 좋은 점은 eval문자열이 아닌 무언가를 전달할 때 아무 것도하지 않기 때문에 오히려 무해합니다if(false){ debugger; }
Benjamin Gruenbaum

6
이 코드는 최근 v8의 변경으로 인해 업데이트되었으므로 이제 생성자를 인스턴스화해야합니다. 그래서 그것은
게으른

4
@BenjaminGruenbaum 왜이 기능이 최적화되지 않아야하는지 자세히 설명 할 수 있습니까? 축소 된 코드에서 eval은 존재하지 않습니다. 축소되지 않은 코드에서 eval이 유용한 이유는 무엇입니까?
Boopathi Rajaa
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.