ฟังก์ชัน util.toFastProperties ของ Bluebird ทำให้คุณสมบัติของวัตถุ“ เร็ว” อย่างไร


165

ในutil.jsไฟล์ของ Bluebird มันมีฟังก์ชั่นดังต่อไปนี้:

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

ด้วยเหตุผลบางอย่างมีคำสั่งหลังจากฟังก์ชันส่งคืนซึ่งฉันไม่แน่ใจว่าทำไมถึงมี

เช่นกันดูเหมือนว่าเป็นเรื่องที่ไตร่ตรองอย่างที่ผู้เขียนได้ทำการเตือน JSHint เกี่ยวกับเรื่องนี้:

เข้าไม่ถึง 'eval' หลังจาก 'return' (W027)

ฟังก์ชันนี้ทำหน้าที่อะไรกันแน่? ไม่util.toFastPropertiesจริงๆทำให้คุณสมบัติของวัตถุ "เร็วขึ้น"?

ฉันค้นหาที่เก็บข้อมูล GitHub ของ Bluebird สำหรับความคิดเห็นใด ๆ ในซอร์สโค้ดหรือคำอธิบายในรายการปัญหาของพวกเขา แต่ฉันไม่พบอะไรเลย

คำตอบ:


314

2017 อัปเดต: ครั้งแรกสำหรับผู้อ่านที่มาในวันนี้ - นี่คือรุ่นที่ทำงานร่วมกับโหนด 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 ใช้การแทนวัตถุสองรายการ:

  • โหมดพจนานุกรม - ในการที่วัตถุจะถูกเก็บไว้เป็นคีย์ - ค่าแผนที่เป็นแผนที่กัญชา
  • โหมดเร็ว - ซึ่งวัตถุจะถูกจัดเก็บเช่นstructsซึ่งไม่มีการคำนวณที่เกี่ยวข้องในการเข้าถึงคุณสมบัติ

นี่คือตัวอย่างง่ายๆที่แสดงให้เห็นถึงความแตกต่างของความเร็ว ที่นี่เราใช้deleteคำสั่งเพื่อบังคับวัตถุเข้าสู่โหมดพจนานุกรมช้า

เอ็นจิ้นพยายามที่จะใช้โหมดรวดเร็วทุกครั้งที่ทำได้และโดยทั่วไปเมื่อใดก็ตามที่มีการเข้าถึงคุณสมบัติจำนวนมาก - อย่างไรก็ตามบางครั้งมันจะถูกโยนเข้าสู่โหมดพจนานุกรม การอยู่ในโหมดพจนานุกรมมีโทษประสิทธิภาพที่สูงดังนั้นโดยทั่วไปแล้วการวางวัตถุในโหมดรวดเร็ว

แฮ็คนี้มีวัตถุประสงค์เพื่อบังคับให้วัตถุเข้าสู่โหมดรวดเร็วจากโหมดพจนานุกรม

ทำไมมันเร็วกว่า

ในต้นแบบจาวาสคริปต์มักจะเก็บฟังก์ชั่นที่ใช้ร่วมกันระหว่างหลาย ๆ กรณีและไม่ค่อยเปลี่ยนแปลงบ่อยนัก ด้วยเหตุนี้จึงเป็นที่พึงปรารถนาอย่างยิ่งที่จะให้พวกเขาอยู่ในโหมดเร็วเพื่อหลีกเลี่ยงการลงโทษพิเศษทุกครั้งที่มีการเรียกใช้ฟังก์ชั่น

สำหรับสิ่งนี้ - 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 และ Bluebird ทำงานบนเบราว์เซอร์ทั้งหมดตั้งแต่ 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 ที่เขียนไปยังโลหะเปลือย นี่เป็นสิ่งที่ดี

Disclaimer:

จำไว้ว่านี่เป็นรายละเอียดการติดตั้งทั้งหมด คนอย่าง Petka นั้นเป็นคนที่คลั่งไคล้ในการเพิ่มประสิทธิภาพ โปรดจำไว้เสมอว่าการเพิ่มประสิทธิภาพก่อนวัยอันควรเป็นรากฐานของความชั่วร้ายทั้งหมด 97% ของเวลา บลูเบิร์ดทำอะไรที่ธรรมดามาก ๆ บ่อยครั้งดังนั้นมันจึงได้รับประโยชน์มากมายจากการแฮ็กประสิทธิภาพเหล่านี้ - เร็วพอ ๆ กับการโทรกลับไม่ใช่เรื่องง่าย คุณไม่ค่อยต้องทำอะไรแบบนี้ในรหัสที่ไม่ได้ใช้พลังงานกับไลบรารี


37
นี่คือโพสต์ที่น่าสนใจที่สุดที่ฉันได้อ่านในขณะที่ ให้ความเคารพและชื่นชมคุณมาก!
m59

2
@ Timoxley ฉันเขียนต่อไปนี้เกี่ยวกับeval(ในความคิดเห็นรหัสเมื่ออธิบายรหัส OP โพสต์): "ป้องกันไม่ให้ฟังก์ชั่นการเพิ่มประสิทธิภาพผ่านการกำจัดรหัสที่ตายแล้วหรือการเพิ่มประสิทธิภาพต่อไปรหัสนี้จะไม่ถึง ฟังก์ชั่น." . นี่คือการอ่านข่าวที่เกี่ยวข้อง คุณต้องการให้ฉันอธิบายเพิ่มเติมเกี่ยวกับเรื่องนี้หรือไม่?
Benjamin Gruenbaum

3
@herman a 1;จะไม่ทำให้เกิด "deoptimization" a debugger;อาจจะทำงานได้ดีพอ ๆ กัน สิ่งที่ดีคือเมื่อevalผ่านสิ่งที่ไม่ใช่สายมันไม่ได้ทำอะไรกับมันเลยดังนั้นมันจึงไม่เป็นอันตราย - เป็นเหมือนif(false){ debugger; }
Benjamin Gruenbaum

6
Btw รหัสนี้ได้รับการปรับปรุงเนื่องจากการเปลี่ยนแปลงใน v8 ล่าสุดตอนนี้คุณต้องสร้างอินสแตนซ์ของตัวสร้างด้วย ดังนั้นจึงกลายเป็น
ขี้เกียจ

4
@BenjaminGruenbaum คุณช่วยอธิบายได้ไหมว่าทำไมฟังก์ชั่นนี้ไม่ควรปรับให้เหมาะสม? ในโค้ดย่อส่วน eval ไม่มีอยู่จริง ทำไม eval ถึงมีประโยชน์ที่นี่ในรหัสที่ไม่ย่อเล็กสุด
Boopathi Rajaa
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.