Bluebird'ün util.toFastProperties işlevi bir nesnenin özelliklerini nasıl “hızlı” yapar?


165

Bluebird util.jsdosyasında aşağıdaki fonksiyona sahiptir:

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

Bazı nedenlerden dolayı, dönüş işlevinden sonra neden orada olduğundan emin olmadığım bir ifade var.

Ayrıca, yazar bu konuda JSHint uyarısını susturduğu için kasıtlı görünüyor:

'Dönüş' sonrasında ulaşılamayan 'eval'. (W027)

Bu işlev tam olarak ne yapıyor? Does util.toFastPropertiesgerçekten "daha hızlı" bir nesnenin özelliklerini yapmak?

Kaynak kodundaki herhangi bir yorum veya sorun listesindeki bir açıklama için Bluebird'in GitHub deposunu aradım, ancak herhangi bir şey bulamadım.

Yanıtlar:


314

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 deletecü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.ccedersek, 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::MigrateSlowToFastsö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- SetPrototypein objects.ccyaparsak, 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 .setPrototypeOfda 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 .setPrototypeOfedersek ş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.


37
Bu, bir süredir okuduğum en ilginç gönderi. Size çok saygı ve takdir!
m59

2
@timoxley eval(OP kodunu açıklarken kod yorumlarında) hakkında aşağıdakileri yazdım : "İşlevin ölü kod ortadan kaldırılması veya daha fazla optimizasyon yoluyla optimize edilmesini önleyin. Bu koda asla ulaşılmaz, ancak erişilemeyen kod bile v8'in optimize edilmemesine neden olur işlevleri." . İşte ilgili bir okuma . Konuyla ilgili daha ayrıntılı bilgi vermemi ister misiniz?
Benjamin Gruenbaum

3
@ dherman a 1;“deoptimizasyon” a neden debugger;olmaz, muhtemelen eşit derecede iyi çalışırdı. Güzel olan şey, evalbir dize olmayan bir şey geçtiğinde, onunla hiçbir şey yapmamasıdır, bu yüzden oldukça zararsızdır - bir türif(false){ debugger; }
Benjamin Gruenbaum

6
Btw bu kod son v8'deki bir değişiklik nedeniyle güncellendi, şimdi yapıcıyı da başlatmanız gerekiyor. Böylece daha
tembelleşti

4
@BenjaminGruenbaum Bu işlevin neden optimize edilmemesi gerektiği konusunda ayrıntılı bilgi verebilir misiniz? Minimize edilmiş kodda, eval zaten mevcut değildir. Değerlendirme burada küçültülmemiş kodda neden yararlıdır?
Boopathi Rajaa
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.