JavaScript'te Nesnelerin / Dizilerin performansı nedir? (özellikle Google V8 için)


105

JavaScript'teki Diziler ve Nesnelerle ilişkili performansı (özellikle Google V8) belgelemek çok ilginç olurdu. İnternetin hiçbir yerinde bu konuyla ilgili kapsamlı bir makale bulamıyorum.

Bazı Nesnelerin temel veri yapıları olarak sınıfları kullandığını anlıyorum. Çok fazla özellik varsa, bazen bir karma tablo muamelesi görür?

Ayrıca Dizilerin bazen C ++ Dizileri gibi ele alındığını da anlıyorum (yani hızlı rastgele indeksleme, yavaş silme ve yeniden boyutlandırma). Ve diğer zamanlarda, daha çok Nesneler gibi işlem görürler (hızlı indeksleme, hızlı ekleme / çıkarma, daha fazla bellek). Ve belki bazen bağlantılı listeler olarak saklanırlar (örn. Yavaş rastgele indeksleme, baştan / sondan hızlı çıkarma / ekleme)

JavaScript'te Dizi / Nesne geri alma ve manipülasyonlarının kesin performansı nedir? (özellikle Google V8 için)

Daha spesifik olarak, performansa etkisi:

  • Bir Nesneye Özellik Ekleme
  • Nesneden bir özelliği kaldırma
  • Bir Nesnedeki bir özelliği dizine ekleme
  • Bir Diziye bir öğe ekleme
  • Diziden bir öğe çıkarma
  • Bir Dizideki bir öğeyi indeksleme
  • Array.pop () çağırılıyor
  • Array.push () çağırılıyor
  • Array.shift () çağırılıyor
  • Array.unshift () çağırılıyor
  • Array.slice () çağırılıyor

Daha fazla ayrıntı için herhangi bir makale veya bağlantı da takdir edilecektir. :)

DÜZENLEME: JavaScript dizilerinin ve nesnelerinin başlık altında nasıl çalıştığını gerçekten merak ediyorum. Ayrıca, V8 motoru hangi bağlamda başka bir veri yapısına "geçiş yapmayı" biliyor?

Örneğin, bir dizi oluşturduğumu varsayalım ...

var arr = [];
arr[10000000] = 20;
arr.push(21);

Burada gerçekten neler oluyor?

Ya da ... buna ne dersin ... ???

var arr = [];
//Add lots of items
for(var i = 0; i < 1000000; i++)
    arr[i] = Math.random();
//Now I use it like a queue...
for(var i = 0; i < arr.length; i++)
{
    var item = arr[i].shift();
    //Do something with item...
}

Geleneksel diziler için performans korkunç olurdu; oysa, bir LinkedList kullanılmışsa ... o kadar da kötü değil.


2
Jsperf.com adresini ziyaret edin ve test senaryoları oluşturun.
Rob W

2
@RobW Burada, JIT derleyicilerinin nasıl çalıştığı ve verilerle ne yapıldığı hakkında bilgi gerektiren basit testlerden daha fazlası var. Biraz zaman bulursam bir cevap eklerim, ama umarım başka birinin bu konuyu ele almak için zamanı olur. Ayrıca bu bağlantıyı burada bırakmak istiyorum
Incognito

Bahsettiğim JIT ile ilgili şeyler, bir nesnenin "şekli" veya tanımlanmış öğeler arasında tanımlanmamış değerlere sahip diziler gibi şeyler ve daha yakın zamanda tür özelleştirme özellikleriyle denenmişlerdir ... diziye özgü yöntemler, ve prototipin manipüle edilmiş olup olmadığı gibi. AFAIK'in başka bir veri türüne geçmeyi "bilmek" diye bir şey yoktur.
Gizli mod

1
Çeşitli optimize edici ve dahili sistemin nasıl çalıştığını tartışan Google temsilcileri var. Ve onlar için nasıl optimize edileceğini. (oyunlar için!) youtube.com/watch?v=XAqIpGU8ZZk
PicoCreator

Yanıtlar:


279

Tam olarak bu sorunları (ve daha fazlasını) ( arşivlenmiş kopya ) keşfetmek için bir test paketi oluşturdum .

Ve bu anlamda, bu 50'den fazla test durumu test cihazında performans sorunlarını görebilirsiniz (uzun zaman alacaktır).

Ayrıca adından da anlaşılacağı gibi, DOM yapısının yerel bağlantılı liste niteliğini kullanmanın kullanımını araştırır.

(Şu anda çalışmıyor, yeniden inşa ediliyor) Bununla ilgili blogumda daha fazla ayrıntı .

Özet aşağıdaki gibidir

  • V8 Dizisi Hızlıdır, ÇOK HIZLI
  • Dizi push / pop / shift herhangi bir nesneye eşdeğerden yaklaşık 20x + daha hızlıdır.
  • Şaşırtıcı bir Array.shift()şekilde hızlıdır ~ bir dizi patlamasından yaklaşık 6 kat daha yavaştır, ancak bir nesne özniteliğini silmeden ~ yaklaşık 100 kat daha hızlıdır.
  • Eğlenceli bir şekilde, neredeyse 20 (dinamik dizi) ila 10 (sabit dizi) kattan Array.push( data );daha hızlıdır Array[nextIndex] = data.
  • Array.unshift(data) beklendiği gibi daha yavaştır ve yeni bir mülk eklenmesinden yaklaşık 5 kat daha yavaştır.
  • Değeri sıfırlamak, bir dizide array[index] = nullonu delete array[index]~ yaklaşık 4x ++ daha hızlı silmekten (tanımsız) daha hızlıdır.
  • Şaşırtıcı bir şekilde bir nesnedeki bir değeri sıfırlamak obj[attr] = null, özniteliği silmekten yaklaşık 2 kat daha yavaştırdelete obj[attr]
  • Şaşırtıcı olmayan bir şekilde, orta dizi Array.splice(index,0,data)yavaş, çok yavaş.
  • Şaşırtıcı bir şekilde Array.splice(index,1,data)optimize edildi (uzunluk değişikliği yok) ve sadece eklemeden 100 kat daha hızlıArray.splice(index,0,data)
  • şaşırtıcı olmayan bir şekilde, divLinkedList, dll.splice(index,1)kaldırma hariç (test sistemini bozduğu yer) tüm sektörlerdeki bir diziden daha düşüktür .
  • EN BÜYÜK SÜRPRİZHepsinden [jjrv'nin belirttiği gibi], V8 dizi yazmaları V8'in okumalarından biraz daha hızlıdır = O

Not: Bu ölçümler yalnızca, v8'in "tamamen optimize etmediği" büyük diziler / nesneler için geçerlidir. Dizi / nesne boyutu için keyfi bir boyuttan (24?) Daha küçük olan çok izole optimize edilmiş performans durumları olabilir. Daha fazla ayrıntı, çeşitli Google IO videolarında kapsamlı bir şekilde görülebilir.

Not 2: Bu harika performans sonuçları tarayıcılar arasında, özellikle de *cough*IE'de paylaşılmaz . Ayrıca test çok büyük, dolayısıyla sonuçları tam olarak analiz edip değerlendirmedim: lütfen içinde düzenleyin =)

Güncellenen Not (Aralık 2012): Google temsilcilerinin youtub'larda chrome'un kendisinin iç işleyişini (bir bağlantılı liste dizisinden sabit bir diziye geçişi vb.) Ve bunların nasıl optimize edileceğini açıklayan videoları vardır. Daha fazla bilgi için GDC 2012: Konsoldan Chrome'a bakın.


2
Bu sonuçlardan bazıları çok tuhaf görünüyor. Örneğin, Chrome'da dizi yazma işlemleri okumadan yaklaşık 10 kat daha hızlıyken, Firefox'ta tam tersi. JIT tarayıcısının bazı durumlarda tüm testinizi optimize etmediğinden emin misiniz?
jjrv

1
@jjrv iyi tanrım = O haklısın ... JIT'yi önlemek için her yazma durumunu aşamalı olarak benzersiz olacak şekilde güncelledim ... Ve dürüst olmak gerekirse, JIT optimizasyonu o kadar iyi değilse (ki buna inanmakta zorlanıyorum), kötü optimize edilmiş bir okuma veya yoğun şekilde optimize edilmiş yazma durumu olabilir (anında arabelleğe yazma?) ... ki bu araştırmaya değer: lol
PicoCreator

2
dizilerle ilgili video tartışmasına tam olarak bir noktayı eklemek istedim: youtube.com/…
badunk

1
JsPerf sitesi artık mevcut değil :(
JustGoscha

1
@JustGoscha tamam, bilgi için teşekkürler: Google önbelleğinden yeniden oluşturarak yedekledim.
PicoCreator

5

JavaScript alanında kalan temel düzeyde, nesneler üzerindeki özellikler çok daha karmaşık varlıklardır. Farklı numaralandırılabilirlik, yazılabilirlik ve yapılandırılabilirlik ile ayarlayıcılar / alıcılar ile özellikler oluşturabilirsiniz. Bir dizideki bir öğe bu şekilde özelleştirilemez: ya vardır ya da yoktur. Altta yatan motor seviyesinde bu, yapıyı temsil eden hafızanın düzenlenmesi açısından çok daha fazla optimizasyona izin verir.

Bir nesneden (sözlük) bir diziyi tanımlama açısından, JS motorları her zaman ikisi arasında açık çizgiler oluşturmuştur. Bu nedenle, biri gibi davranan ancak diğer işlevlere izin veren yarı sahte Dizi benzeri bir nesne oluşturmaya çalışma yöntemleri hakkında çok sayıda makale var. Bu ayrımın var olmasının nedeni, JS motorlarının ikisini farklı şekilde depolamasıdır.

Özellikler bir dizi nesnesinde saklanabilir ancak bu, JavaScript'in her şeyi bir nesne yapmakta ne kadar ısrar ettiğini gösterir. Bir dizideki dizinlenmiş değerler, temeldeki dizi verilerini temsil eden dizi nesnesinde ayarlamaya karar verdiğiniz özelliklerden farklı şekilde depolanır.

Yasal bir dizi nesnesi kullandığınızda ve bu diziyi değiştirmenin standart yöntemlerinden birini kullandığınızda, temeldeki dizi verilerine ulaşacaksınız. V8'de özellikle, bunlar temelde bir C ++ dizisi ile aynıdır, bu nedenle bu kurallar geçerli olacaktır. Herhangi bir nedenle motorun güvenle belirleyemediği bir dizi üzerinde çalışıyorsanız bir dizi ise, o zaman çok daha sarsıntılı bir yerdesiniz demektir. V8'in son sürümlerinde çalışmak için daha fazla alan var. Örneğin, prototipi Array.prototype olan bir sınıf oluşturmak mümkündür. ve yine de çeşitli yerel dizi işleme yöntemlerine verimli erişim sağlayan . Ancak bu yeni bir değişiklik.

Dizi manipülasyonunda yapılan son değişikliklere yönelik belirli bağlantılar burada kullanışlı olabilir:

Fazladan bir parça olarak burada, her ikisi de JS'de uygulanan, doğrudan V8'in kaynağından Array Pop ve Array Push var:

function ArrayPop() {
  if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
    throw MakeTypeError("called_on_null_or_undefined",
                        ["Array.prototype.pop"]);
  }

  var n = TO_UINT32(this.length);
  if (n == 0) {
    this.length = n;
    return;
  }
  n--;
  var value = this[n];
  this.length = n;
  delete this[n];
  return value;
}


function ArrayPush() {
  if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
    throw MakeTypeError("called_on_null_or_undefined",
                        ["Array.prototype.push"]);
  }

  var n = TO_UINT32(this.length);
  var m = %_ArgumentsLength();
  for (var i = 0; i < m; i++) {
    this[i+n] = %_Arguments(i);
  }
  this.length = n + m;
  return this.length;
}

1

Mevcut yanıtları, uygulamaların artan dizilerle ilgili nasıl davrandığı sorusuna yönelik bir araştırmayla tamamlamak istiyorum: Eğer onları "olağan" şekilde uygularlarsa, nadir, serpiştirilmiş yavaş itmelerle birçok hızlı itme görür ve bu noktada uygulama kopyalanır dizinin bir arabellekten daha büyük olana dahili gösterimi.

Bu etkiyi çok güzel görebilirsiniz, bu Chrome'dan:

16: 4ms
40: 8ms 2.5
76: 20ms 1.9
130: 31ms 1.7105263157894737
211: 14ms 1.623076923076923
332: 55ms 1.5734597156398105
514: 44ms 1.5481927710843373
787: 61ms 1.5311284046692606
1196: 138ms 1.5196950444726811
1810: 139ms 1.5133779264214047
2731: 299ms 1.5088397790055248
4112: 341ms 1.5056755767118273
6184: 681ms 1.5038910505836576
9292: 1324ms 1.5025873221216042

Her bir itme profilli olsa da, çıktı yalnızca belirli bir eşiğin üzerinde zaman alanları içerir. Her test için eşiği hızlı itmeleri temsil ediyor gibi görünen tüm itmeleri hariç tutacak şekilde özelleştirdim.

Dolayısıyla, ilk sayı hangi öğenin eklendiğini (ilk satır 17. öğe içindir), ikincisi ne kadar sürdüğünü (birçok dizi için kıyaslama paralel olarak yapılır) ve son değerin bölümüdür. ilk numara, önceki satırdakine göre.

2ms'den daha az yürütme süresine sahip tüm satırlar Chrome için hariç tutulmuştur.

Chrome'un dizi boyutunu 1.5'in katlarına çıkardığını ve ayrıca küçük dizileri hesaba katmak için biraz ofseti artırdığını görebilirsiniz.

Firefox için bu ikisinin gücü:

126: 284ms
254: 65ms 2.015873015873016
510: 28ms 2.0078740157480315
1022: 58ms 2.003921568627451
2046: 89ms 2.0019569471624266
4094: 191ms 2.0009775171065494
8190: 364ms 2.0004885197850513

Firefox'ta eşiği biraz yükseltmem gerekti, bu yüzden 126 numaradan başlıyoruz.

IE ile bir karışım elde ediyoruz:

256: 11ms 256
512: 26ms 2
1024: 77ms 2
1708: 113ms 1.66796875
2848: 154ms 1.6674473067915691
4748: 423ms 1.6671348314606742
7916: 944ms 1.6672283066554338

İlk başta ikinin gücüdür ve sonra üçte beşin gücüne geçer.

Bu nedenle, tüm yaygın uygulamalar diziler için "normal" yolu kullanır ( örneğin iplerle delirmek yerine ).

İşte kıyaslama kodu ve işte içinde bulunduğu keman .

var arrayCount = 10000;

var dynamicArrays = [];

for(var j=0;j<arrayCount;j++)
    dynamicArrays[j] = [];

var lastLongI = 1;

for(var i=0;i<10000;i++)
{
    var before = Date.now();
    for(var j=0;j<arrayCount;j++)
        dynamicArrays[j][i] = i;
    var span = Date.now() - before;
    if (span > 10)
    {
      console.log(i + ": " + span + "ms" + " " + (i / lastLongI));
      lastLongI = i;
    }
}

0

Node.js 0.10 (v8 üzerinde oluşturulmuş) altında çalışırken, iş yükü için aşırı görünen CPU kullanımı görüyordum. Bir dizide bir dizenin varlığını kontrol eden bir işleve bir performans sorununu izledim. Bu yüzden bazı testler yaptım.

  • 90.822 ana bilgisayar yüklendi
  • yükleme yapılandırması 0.087 saniye sürdü (dizi)
  • yükleme yapılandırması 0.152 saniye sürdü (nesne)

Bir diziye 91 bin giriş yüklemek (doğrulama ve itme ile), obj [anahtar] = değer ayarlamaktan daha hızlıdır.

Bir sonraki testte, listedeki her ana bilgisayar adını bir kez aradım (arama süresinin ortalamasını almak için 91k yineleme):

  • arama yapılandırması 87.56 saniye sürdü (dizi)
  • arama yapılandırması 0.21 saniye sürdü (nesne)

Buradaki uygulama Haraka'dır (bir SMTP sunucusu) ve host_list'i başlangıçta bir kez (ve değişikliklerden sonra) yükler ve daha sonra bu aramayı işlem sırasında milyonlarca kez gerçekleştirir. Bir nesneye geçiş, büyük bir performans kazanımı oldu.

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.