V8'de JavaScript için performans ipuçları

Chris Wilson
Chris Wilson

Giriş

Daniel Clifford, V8'de JavaScript performansını iyileştirmeye yönelik ipuçları ve püf noktaları hakkında Google I/O'da mükemmel bir konuşma yaptı. Daniel, "daha hızlı talep etmemiz" için bizi teşvik etti - C++ ve JavaScript arasındaki performans farklarını dikkatlice analiz edin ve JavaScript'in çalışma şekline dikkat ederek kod yazın. Doğan'ın konuşmasının en önemli noktaları bu makalede özetlenmiştir. Ayrıca performans kılavuzu değiştikçe bu makaleyi de güncelleyeceğiz.

En Önemli Tavsiye

Performans tavsiyelerini bağlama oturtmanız önemlidir. Performans önerileri bağımlılık yapar ve bazen önce derin tavsiyelere odaklanmak, dikkati asıl sorunlardan saptırabilir. Web uygulamanızın performansına dair bütünsel bir bakış açısı elde etmeniz gerekir. Bu performans ipucuna odaklanmadan önce büyük olasılıkla kodunuzu PageSpeed gibi araçlarla analiz etmeniz ve puanınızı yükseltmeniz gerekir. Bu, erken optimizasyondan kaçınmanıza yardımcı olur.

Web uygulamalarında iyi performans elde etmek için en iyi temel öneriler şu şekildedir:

  • Sorun yaşamadan (veya fark etmeden) önce hazırlıklı olma
  • Ardından sorununuzun temel noktasını belirleyin ve anlayın
  • Son olarak, önemli noktaları düzeltin

Bu adımları tamamlamak için V8'in JS'yi nasıl optimize ettiğini anlamak önemlidir. Böylece JS çalışma zamanı tasarımına dikkat ederek kod yazabilirsiniz. Kullanabileceğiniz araçlar ve bunların size nasıl yardımcı olabileceği hakkında bilgi edinmek de önemlidir. Daniel, konuşmasında geliştirici araçlarını nasıl kullanacağıyla ilgili daha fazla açıklama yapıyor. bu belgede, V8 motoru tasarımının en önemli noktalarından bazıları ele alınmaktadır.

V8 ipuçlarına geçelim!

Gizli Sınıflar

JavaScript'in derleme süresi türü bilgileri sınırlıdır: türler çalışma zamanında değiştirilebilir. Bu nedenle, derleme sırasında JS türleriyle ilgili akıl yürütmenin pahalı olması normaldir. Bu da sizi JavaScript performansının C++'a yakın bir yerde nasıl ulaşabileceğini sorgulamaya itebilir. Ancak V8'in çalışma zamanında nesneler için dahili olarak oluşturulmuş gizli türleri vardır. Böylece aynı gizli sınıfa sahip nesneler optimize edilmiş aynı kodu kullanabilir.

Örneğin:

function Point(x, y) {
  this.x = x;
  this.y = y;
}

var p1 = new Point(11, 22);
var p2 = new Point(33, 44);
// At this point, p1 and p2 have a shared hidden class
p2.z = 55;
// warning! p1 and p2 now have different hidden classes!```

p2 nesne örneği ".z" ek üyesine sahip olana kadar p1 ve p2 dahili olarak aynı gizli sınıfa sahiptir. Böylece V8, p1 veya p2'yi değiştiren JavaScript kodu için optimize edilmiş derlemenin tek bir sürümünü oluşturabilir. Gizli sınıfların birbirinden ayrılmasını ne kadar çok önleyebilirseniz daha iyi performans elde edersiniz.

Bu nedenle

  • Yapıcı işlevlerindeki tüm nesne üyelerini başlatın (böylece örneklerin türü daha sonra değişmemesi için)
  • Nesne üyelerini her zaman aynı sırayla başlat

Numbers

V8, türler değişebildiğinde değerleri verimli bir şekilde temsil etmek için etiketlemeyi kullanır. V8, incelediğiniz sayı türünü kullandığınız değerlerden çıkarım yapar. V8 bu çıkarımı yaptıktan sonra, bu türler dinamik bir şekilde değişebileceğinden, değerleri verimli bir şekilde temsil etmek için etiketlemeyi kullanır. Bununla birlikte, bu tür etiketleri değiştirmenin bazen bir maliyeti vardır. Bu nedenle, sayı türlerini tutarlı bir şekilde kullanmak en iyisidir ve genel olarak, uygun durumlarda 31 bit imzalı tam sayıları kullanmak en uygun seçenektir.

Örneğin:

var i = 42;  // this is a 31-bit signed integer
var j = 4.2;  // this is a double-precision floating point number```

Bu nedenle

  • 31 bit imzalı tam sayılar olarak temsil edilebilecek sayısal değerleri tercih edin.

Diziler

Büyük ve seyrek dizileri işlemek için dahili olarak iki tür dizi depolama alanı vardır:

  • Hızlı Öğeler: Kompakt anahtar kümeleri için doğrusal depolama
  • Sözlük Öğeleri: Aksi takdirde karma tablo depolama

Dizi depolama alanının bir türden diğerine dönmesine yol açmamak en iyisidir.

Bu nedenle

  • Diziler için 0'dan başlayan bitişik tuşlar kullan
  • Büyük dizileri (ör. > 64K öğe) maksimum boyutlarına göre önceden ayırmayın, oynadıkça büyüyün
  • Dizilerdeki öğeleri, özellikle de sayısal dizileri silmeyin
  • Başlatılmamış veya silinmiş öğeleri yüklemeyin:
for (var b = 0; b < 10; b++) {
  a[0] |= b;  // Oh no!
}
//vs.
a = new Array();
a[0] = 0;
for (var b = 0; b < 10; b++) {
  a[0] |= b;  // Much better! 2x faster.
}

Ayrıca, çiftler dizileri daha hızlıdır. Dizinin gizli sınıftaki öğe türleri bulunur ve yalnızca çiftler içeren diziler kutulardan çıkarılır (gizli sınıf değişikliğine neden olur). Ancak dizilerin dikkatsizce değiştirilmesi, boks ve kutu açılımı nedeniyle fazladan iş yapılmasına neden olabilir.

var a = new Array();
a[0] = 77;   // Allocates
a[1] = 88;
a[2] = 0.5;   // Allocates, converts
a[3] = true; // Allocates, converts```

şundan daha az verimlidir:

var a = [77, 88, 0.5, true];

Çünkü ilk örnekte tek tek atamalar birbiri ardına gerçekleştirilir ve a[2] ataması, Dizinin kutusu açılmış bir çiftler dizisine dönüştürülmesine neden olur, ancak a[3] ataması, dizinin tekrar herhangi bir değer (Sayılar veya nesneler) içerebilen bir diziye dönüştürülmesine neden olur. İkinci durumda, derleyici değişmez değerdeki tüm öğelerin türlerini bilir ve gizli sınıf önceden belirlenebilir.

  • Küçük sabit boyutlu diziler için değişmez dizi değerlerini kullanarak başlatma
  • Küçük dizileri (<64k) kullanmadan önce doğru boyutlara önceden ayırma
  • Sayısal dizilerde sayısal olmayan değerleri (nesneler) depolamayın
  • Değişmez değer olmadan başlatırsanız küçük dizilerin yeniden dönüştürülmesine yol açmamaya dikkat edin.

JavaScript Derlemesi

JavaScript çok dinamik bir dil olmasına ve orijinal uygulamaları çevirmenlerdi, ancak modern JavaScript çalışma zamanı motorları derleme kullanır. V8'in (Chrome JavaScript'i) iki farklı Just-In-Time (JIT) derleyicisi vardır:

  • "Tam" her tür JavaScript için iyi kod oluşturabilen bir derleyici
  • Çoğu JavaScript için harika kod üreten, ancak derlenmesi daha uzun süren Optimize derleyici.

Tam Derleyici

V8'de Tam derleyici, tüm kod üzerinde çalışır ve kodu mümkün olan en kısa sürede yürütmeye başlar. Böylece, hızlı bir şekilde iyi ama çok iyi olmayan kod üretir. Bu derleyici, derleme sırasında türler hakkında neredeyse hiçbir şey varsaymaz. Değişken türlerinin çalışma zamanında değişebileceğini ve değişmesini bekler. Tam derleyici tarafından oluşturulan kod, program çalışırken türler hakkındaki bilgileri hassaslaştırmak için satır içi önbellekleri (IC'ler) kullanır ve böylece verimliliği artırır.

Satır İçi Önbelleklerin amacı, işlemler için türe bağlı kodu önbelleğe alarak türleri verimli bir şekilde işlemektir. çalıştırıldığında, önce tür varsayımlarını doğrular, ardından işlemin kısayolunu kullanmak için satır içi önbelleği kullanır. Ancak bu, birden fazla türü kabul eden işlemlerin daha düşük performans göstereceği anlamına gelir.

Bu nedenle

  • İşlemlerin polimorfik işlemlere göre monomorfik kullanımı tercih edilir

Gizli giriş sınıfları her zaman aynıysa işlemler monomorfiktir. Aksi takdirde, polimorfik olurlar. Diğer bir deyişle, bazı bağımsız değişkenlerin, işleme yönelik farklı çağrılarda türü değişebilir. Örneğin, bu örnekteki ikinci add() çağrısı polimorfizme neden olur:

function add(x, y) {
  return x + y;
}

add(1, 2);      // + in add is monomorphic
add("a", "b");  // + in add becomes polymorphic```

Optimize Etme Derleyici

V8, tam derleyiciye paralel olarak "hot" öğesini yeniden derler işlevlerine (yani, birçok kez çalıştırılan işlevlere) bir optimizasyon derleyicisi ekler. Bu derleyici, derlenen kodu daha hızlı hale getirmek için tür geri bildiriminden yararlanır. Hatta, az önce bahsettiğimiz IC'lerden alınan türleri kullanır.

Optimize eden derleyicide işlemler tahmine dayalı olarak iç içe yerleştirilir (doğrudan çağrıldıkları yere yerleştirilir). Bu, yürütme işlemini hızlandırır (bellek ayak izi pahasına), ancak diğer optimizasyonlara da olanak sağlar. Tek tip fonksiyonlar ve kurucular tamamen satır içine alınabilir (V8'de monomorfizmin iyi bir fikir olmasının bir başka nedeni de budur).

Bağımsız "d8" kullanarak optimize edilen öğeleri günlüğe kaydedebilirsiniz sürümü:

d8 --trace-opt primes.js

(bu işlem, optimize edilen işlevlerin adlarını stdout'a kaydeder.)

Ancak tüm işlevler optimize edilemez. Bazı özellikler, optimize eden derleyicinin belirli bir işlevde ("kurtarma") çalışmasını engeller. Özellikle, optimize eden derleyici şu anda işlevleri {} yakalama {} blokları kullanarak başarıyor.

Bu nedenle

  • {} yakalama {} bloklarını denediyseniz, iç içe geçmiş bir işleve perf hassasiyeti kodu yerleştirin: ```js function perf_sensitive() { // Performans açısından hassas bir işlem yapın }

deneyin { perf_sensitive() } catch (e) { // İstisnaları burada işleyin } ```

Optimizasyon derleyicisinde dene/yakala bloklarını etkinleştirdiğimiz için bu kılavuz gelecekte muhtemelen değişecektir. Optimize eden derleyicinin işlevlerde nasıl devre dışı bıraktığını incelemek için "--trace-opt" d8'li seçeneği görebilirsiniz. Bu seçenek, hangi işlevlerin kurtarıldığı hakkında daha fazla bilgi sağlar:

d8 --trace-opt primes.js

Optimizasyonun iptal edilmesi

Son olarak, bu derleyici tarafından gerçekleştirilen optimizasyon tahmine dayalıdır; bazen işe yaramaz ve işlemi iptal ederiz. "Optimizasyonu iptal etme" süreci optimize edilmiş kodu atar ve "tam" modunda doğru yerde yürütmeye devam eder derleyici kodudur. Yeniden optimizasyon daha sonra tekrar tetiklenebilir, ancak kısa vadede ise uygulama yavaşlar. Özellikle, işlevler optimize edildikten sonra gizli değişken sınıflarında değişiklik yapılması, bu optimizasyonun iptal edilmesine neden olur.

Bu nedenle

  • Optimize edildikten sonra işlevlerde gizli sınıf değişikliklerini önleme

Diğer optimizasyonlarda olduğu gibi, bir günlük kaydı işareti kullanarak V8'in optimizasyonundan vazgeçmesi gereken işlevlerin günlüğünü alabilirsiniz:

d8 --trace-deopt primes.js

Diğer V8 Araçları

Bu arada, V8 izleme seçeneklerini başlangıçta Chrome'a da aktarabilirsiniz:

"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --js-flags="--trace-opt --trace-deopt"```

Geliştirici araçları profil çıkarmayı kullanmanın yanı sıra, profil oluşturmak için de d8 kullanabilirsiniz:

% out/ia32.release/d8 primes.js --prof

Bu komut, her milisaniyede bir örnek alan ve v8.log yazan yerleşik örnekleme profil aracını kullanır.

Özet

V8 motorunun, yüksek performanslı JavaScript oluşturmaya hazırlanmak için kodunuzla nasıl çalışacağını tanımlamak ve anlamak önemlidir. Bir kez daha temel tavsiye:

  • Sorun yaşamadan (veya fark etmeden) önce hazırlıklı olma
  • Ardından sorununuzun temel noktasını belirleyin ve anlayın
  • Son olarak, önemli noktaları düzeltin

Bu, öncelikle PageSpeed gibi başka araçları kullanarak sorunun JavaScript'inizde olduğundan emin olmanız gerektiği anlamına gelir; metrikleri toplamadan önce yalnızca JavaScript'e (DOM değil) indirgenebilir ve ardından bu metrikleri kullanarak performans sorunlarını bulup önemli olanları ortadan kaldırabilirsiniz. Umarım Daniel'ın konuşması (ve bu makalenin) V8'in JavaScript'i nasıl çalıştırdığını daha iyi anlamanıza yardımcı olur. Yine de kendi algoritmalarınızı optimize etmeye odaklanmayı unutmayın!

Referanslar