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