2017 güncellemesi: İlk olarak, bugün gelen okuyucular için - İşte Node 7 (4+) ile çalışan bir sürüm:
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
}
Bir veya iki küçük optimizasyon yapın - aşağıdakilerin hepsi hala geçerlidir.
Önce ne yaptığını ve neden daha hızlı ve sonra neden çalıştığını tartışalım.
Bu ne yapar
V8 motoru iki nesne temsili kullanır:
- Sözlük modu - nesnenin bir karma harita olarak anahtar / değer eşlemeleri olarak saklandığı .
- Hızlı mod - nesnelerin yapılar gibi saklandığı , mülk erişiminde herhangi bir hesaplamanın bulunmadığı.
İşte hız farkını gösteren basit bir demo . Burada delete
cümleyi yavaş sözlük moduna zorlamak için ifadeyi kullanıyoruz .
Motor mümkün olduğunda ve genellikle çok sayıda özellik erişimi gerçekleştirildiğinde hızlı modu kullanmaya çalışır - ancak bazen sözlük moduna geçer. Sözlük modunda olmanın büyük bir performans cezası vardır, bu nedenle genellikle nesneleri hızlı moda koymak istenir.
Bu saldırı, nesneyi sözlük modundan hızlı moda zorlamaya yöneliktir.
Neden daha hızlı
JavaScript prototiplerinde genellikle birçok örnek arasında paylaşılan işlevleri depolar ve nadiren çok dinamik olarak değişir. Bu nedenle, her işlev çağrıldığında ekstra cezadan kaçınmak için hızlı modda olmaları çok arzu edilir.
Bunun için - v8 .prototype
, işlevlerin özelliği olan nesneleri memnuniyetle hızlı modda koyacaktır, çünkü bu işlev bir kurucu olarak çağrılarak oluşturulan her nesne tarafından paylaşılacaktır. Bu genellikle akıllı ve istenen bir optimizasyondur.
Nasıl çalışır
Önce kodu gözden geçirelim ve her satırın ne yaptığını anlayalım:
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.
}
Biz yok olması , bunun yerine bu optimizasyon yapabilirsiniz olmadığını v8'i iddia kodunu kendimizi bulmak için v8 ünite testleri okumak :
// 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));
Bu testi okumak ve çalıştırmak bize bu optimizasyonun gerçekten v8'de çalıştığını gösteriyor. Ancak - nasıl olduğunu görmek güzel olurdu.
Kontrol objects.cc
edersek, aşağıdaki işlevi bulabiliriz (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);
}
}
Şimdi, JSObject::MigrateSlowToFast
sözlüğü açıkça alır ve hızlı bir V8 nesnesine dönüştürür. V8 nesnesi içlerine ilişkin değerli bir okuma ve ilginç bir içgörü - ama burada konu değil. V8 nesneleri hakkında bilgi edinmek için iyi bir yol olduğu için burada okumanızı şiddetle tavsiye ediyorum .
Check- SetPrototype
in objects.cc
yaparsak, bunun 12231 satırında çağrıldığını görebiliriz:
if (value->IsJSObject()) {
JSObject::OptimizeAsPrototype(Handle<JSObject>::cast(value));
}
Hangi sırayla denir FuntionSetPrototype
hangi biz elde ediyoruz .prototype =
.
Yapmak __proto__ =
ya .setPrototypeOf
da işe yarayacaktı, ancak bunlar ES6 işlevleri ve Bluebird, Netscape 7'den beri tüm tarayıcılarda çalışıyor, bu yüzden burada kodu basitleştirmek söz konusu değil. Örneğin, kontrol .setPrototypeOf
edersek şunu görebiliriz:
// 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;
}
Doğrudan olan Object
:
InstallFunctions($Object, DONT_ENUM, $Array(
...
"setPrototypeOf", ObjectSetPrototypeOf,
...
));
Yani - Petka'nın yazdığı koddan çıplak metale doğru yürüdük. Bu güzeldi.
Yasal Uyarı:
Tüm bunların uygulama detayı olduğunu unutmayın. Petka gibi insanlar optimizasyon düşkünleri. Erken optimizasyonun zamanın% 97'sinin tüm kötülüklerinin kökü olduğunu unutmayın. Bluebird çok temel bir şey yapar, bu nedenle bu performans hack'lerinden çok kazanır - geri aramalar kadar hızlı olmak kolay değildir. Sen nadiren bir kütüphane güç gelmez kodunda böyle bir şey yapmak zorunda.