Pembaruan 2017: Pertama, untuk pembaca yang datang hari ini - ini adalah versi yang berfungsi dengan 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 satu atau dua optimasi kecil - semua di bawah ini masih berlaku.
Pertama mari kita bahas apa yang dilakukannya dan mengapa itu lebih cepat dan kemudian mengapa itu berhasil.
Apa yang dilakukannya
Mesin V8 menggunakan dua representasi objek:
- Mode kamus - di mana objek disimpan sebagai kunci - peta nilai sebagai peta hash .
- Mode cepat - di mana objek disimpan seperti struct , di mana tidak ada perhitungan yang terlibat dalam akses properti.
Berikut ini adalah demo sederhana yang menunjukkan perbedaan kecepatan. Di sini kita menggunakan delete
pernyataan untuk memaksa objek ke mode kamus lambat.
Mesin mencoba menggunakan mode cepat bila memungkinkan dan umumnya setiap kali banyak akses properti dilakukan - namun terkadang ia dilemparkan ke mode kamus. Berada dalam mode kamus memiliki penalti performa yang besar sehingga umumnya diinginkan untuk meletakkan objek dalam mode cepat.
Retasan ini dimaksudkan untuk memaksa objek ke mode cepat dari mode kamus.
Kenapa lebih cepat
Dalam JavaScript prototipe biasanya menyimpan fungsi yang dibagikan di antara banyak contoh dan jarang berubah banyak secara dinamis. Untuk alasan ini sangat diinginkan untuk memilikinya dalam mode cepat untuk menghindari penalti tambahan setiap kali suatu fungsi dipanggil.
Untuk ini - v8 akan dengan senang hati meletakkan objek yang merupakan .prototype
properti fungsi dalam mode cepat karena mereka akan dibagikan oleh setiap objek yang dibuat dengan menggunakan fungsi tersebut sebagai konstruktor. Ini pada umumnya optimasi yang cerdas dan diinginkan.
Bagaimana itu bekerja
Mari kita pertama-tama membaca kode dan mencari tahu apa yang dilakukan setiap baris:
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.
}
Kita tidak harus menemukan kode sendiri untuk menyatakan bahwa v8 melakukan optimasi ini, kita bisa membaca tes unit 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));
Membaca dan menjalankan tes ini menunjukkan kepada kita bahwa optimasi ini memang berfungsi di v8. Namun - akan menyenangkan untuk melihat caranya.
Jika kita periksa, objects.cc
kita dapat menemukan fungsi berikut (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);
}
}
Sekarang, JSObject::MigrateSlowToFast
secara eksplisit mengambil Kamus dan mengubahnya menjadi objek V8 cepat. Ini adalah bacaan yang bermanfaat dan wawasan yang menarik tentang objek internal v8 - tapi itu bukan subjek di sini. Saya masih dengan hangat menyarankan agar Anda membacanya di sini karena ini adalah cara yang baik untuk belajar tentang objek v8.
Jika kita check- SetPrototype
in objects.cc
, kita dapat melihat bahwa itu disebut dalam baris 12231:
if (value->IsJSObject()) {
JSObject::OptimizeAsPrototype(Handle<JSObject>::cast(value));
}
Yang pada gilirannya disebut dengan FuntionSetPrototype
apa yang kita dapatkan .prototype =
.
Melakukan __proto__ =
atau .setPrototypeOf
mungkin juga bekerja tetapi ini adalah fungsi ES6 dan Bluebird berjalan di semua browser sejak Netscape 7 sehingga tidak ada pertanyaan untuk menyederhanakan kode di sini. Misalnya, jika kita periksa, .setPrototypeOf
kita dapat melihat:
// 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;
}
Yang langsung aktif Object
:
InstallFunctions($Object, DONT_ENUM, $Array(
...
"setPrototypeOf", ObjectSetPrototypeOf,
...
));
Jadi - kita telah menapaki jalan dari kode yang ditulis Petka ke bare metal. Ini bagus.
Penolakan:
Ingat ini semua detail implementasi. Orang-orang seperti Petka adalah penggila optimasi. Selalu ingat bahwa pengoptimalan prematur adalah akar dari semua kejahatan 97% dari waktu. Bluebird sering melakukan sesuatu yang sangat mendasar sehingga mendapatkan banyak keuntungan dari peretasan kinerja ini - menjadi secepat panggilan balik tidak mudah. Anda jarang harus melakukan sesuatu seperti ini dalam kode yang tidak mendukung perpustakaan.