Neden V8'de bu kod snippet'ini kullanmak <= daha yavaş?


166

Slaytlar V8 ile Javascript Hız Sınırı Breaking okuyorum ve aşağıdaki kod gibi bir örnek var. Neden bu durumda <=olduğundan daha yavaş olduğunu anlayamıyorum , <kimse bunu açıklayabilir mi? Herhangi bir yorum için teşekkür ederiz.

Yavaş:

this.isPrimeDivisible = function(candidate) {
    for (var i = 1; i <= this.prime_count; ++i) {
        if (candidate % this.primes[i] == 0) return true;
    }
    return false;
} 

(İpucu: primes, prime_count uzunluk dizisidir)

Daha hızlı:

this.isPrimeDivisible = function(candidate) {
    for (var i = 1; i < this.prime_count; ++i) {
        if (candidate % this.primes[i] == 0) return true;
    }
    return false;
} 

[Daha Fazla Bilgi] hız artışı önemlidir, benim yerel çevre test, sonuçları aşağıdaki gibidir:

V8 version 7.3.0 (candidate) 

Yavaş:

 time d8 prime.js
 287107
 12.71 user 
 0.05 system 
 0:12.84 elapsed 

Daha hızlı:

time d8 prime.js
287107
1.82 user 
0.01 system 
0:01.84 elapsed

10
@DacreDenny Hem modern işlemcilerin (ve tercümanların) hem teoride hem de gerçek uygulamada hesaplama zorluğu <=ve <aynıdır.
TypeIA

1
Belgeyi okudum, zamanlar mainçalışan bir döngüde bu işlevi çağıran bir kod var 25000, bu yüzden genel olarak bu değişikliği yapan daha az yineleme çok yapıyorsunuz. Ayrıca, bir dizinin uzunluğu 5 ise, diziler dizine eklenmeye başladığı için array[5]bir undefineddeğer vererek sınırını aşacaktır 0.
Shidersz

1
Bu sorunun ne kadar bir hız geliştirmesinin ne kadar kazanıldığını (örneğin, 5 kat daha hızlı) açıkladığı yararlı olur, böylece insanlar ekstra yineleme ile atılmazlar. Slaytlarda ne kadar hızlı bulmaya çalıştım ama çok şey vardı ve onu bulmakta zorlandım, aksi takdirde kendim düzenlerdim.
Kaptan Adam

@CaptainMan Haklısın, tam hız iyileştirmesini slaytlardan çıkarmak zordur, çünkü aynı anda birkaç farklı sorunu kapsarlar. Ancak bu konuşmadan sonra konuşmacı ile konuşmamda, bu test örneğindeki ekstra bir yinelemeden beklediğiniz gibi sadece yüzde küçük bir kısmı olmadığını, büyük bir fark olduğunu doğruladı: birkaç kat daha hızlı, belki de bir sipariş büyüklük veya daha fazla. Bunun nedeni, dizi sınırlarının dışında okumaya çalıştığınızda V8'in optimize edilmemiş dizi biçimine geri dönmesi (veya o günlerde geri düşmesi).
Michael Geary

3
Kullanımlar bir versiyonunu karşılaştırmak yararlı olabilir <=ama aksi için aynı hareket <yaparak sürümü i <= this.prime_count - 1. Bu, hem "ekstra yineleme" sorununu hem de "dizinin sonunu aşan" sorununu çözer.
TheHansinator

Yanıtlar:


132

Google'da V8 üzerinde çalışıyorum ve mevcut cevaplar ve yorumlarla ilgili ek bilgiler vermek istedim.

Başvuru için, slaytlardaki tam kod örneğini burada bulabilirsiniz :

var iterations = 25000;

function Primes() {
  this.prime_count = 0;
  this.primes = new Array(iterations);
  this.getPrimeCount = function() { return this.prime_count; }
  this.getPrime = function(i) { return this.primes[i]; }
  this.addPrime = function(i) {
    this.primes[this.prime_count++] = i;
  }
  this.isPrimeDivisible = function(candidate) {
    for (var i = 1; i <= this.prime_count; ++i) {
      if ((candidate % this.primes[i]) == 0) return true;
    }
    return false;
  }
};

function main() {
  var p = new Primes();
  var c = 1;
  while (p.getPrimeCount() < iterations) {
    if (!p.isPrimeDivisible(c)) {
      p.addPrime(c);
    }
    c++;
  }
  console.log(p.getPrime(p.getPrimeCount() - 1));
}

main();

Her şeyden önce, performans farkının doğrudan <ve <=operatörlerle hiçbir ilgisi yoktur . Bu yüzden lütfen <=kodunuzda sakınmak için çembere zıplamayın, çünkü Stack Overflow'da yavaş olduğunu okuyorsunuz --- değil!


İkincisi, millet dizinin "holey" olduğuna dikkat çekti. Bu, OP'nin gönderisindeki kod snippet'inden net değildi, ancak başlatan koda baktığınızda açıktır this.primes:

this.primes = new Array(iterations);

İle bir dizideki Bu sonuçlar bir HOLEYtür elemanlar dizisi biter bile V8 içinde tamamen bitişik / paketlenmiş / doldurdu. Genel olarak, delikli dizilerdeki işlemler paketlenmiş dizilerdeki işlemlerden daha yavaştır, ancak bu durumda fark önemsizdir: içindeki döngüye her bastığımızda 1 ek Smi ( küçük tamsayı ) kontrolü (deliklere karşı korumak için) anlamına gelir . Önemli değil!this.primes[i]isPrimeDivisible

TL; DR Buradaki dizi HOLEYsorunu değil.


Diğerleri ise kodun sınırların dışında olduğunu belirtti. Genellikle dizilerin uzunluğunun ötesinde okumaktan kaçınılması önerilir ve bu durumda aslında performanstaki büyük düşüşten kaçınırdı. Ama neden? V8, bu sınır dışı senaryoların bazılarını yalnızca küçük bir performans etkisi ile işleyebilir. Öyleyse bu dava hakkında özel olan ne?

Out-of-sınırları içinde sonuçlarını okumak this.primes[i]olmanın undefinedbu hat üzerinde:

if ((candidate % this.primes[i]) == 0) return true;

Bu da bizi asıl konuya getiriyor : %operatör şimdi tamsayı olmayan işlenenlerle kullanılıyor!

  • integer % someOtherIntegerçok verimli bir şekilde hesaplanabilir; JavaScript motorları bu durum için yüksek düzeyde optimize edilmiş makine kodu üretebilir.

  • integer % undefineddiğer yandan Float64Mod, undefinedbir çift olarak temsil edildiğinden , daha az verimli bir yol anlamına gelir .

Kod parçacığı gerçekten değiştirilerek iyileştirilebilir <=INTO <bu hatta:

for (var i = 1; i <= this.prime_count; ++i) {

... <=bir şekilde daha üstün bir operatör olduğu için değil <, bunun sadece bu özel durumda sınırların dışına çıkmasını önlediği için.


1
Yorumlar uzun tartışmalar için değildir; bu görüşme sohbete taşındı .
Samuel Liew

1
% 100 tamamlanması için, bunun için anahtarlı yük IC'si. İsPrimeDivisible içindeki [i] anahtarları beklenmedik bir şekilde V8'de megamorfik olur. Bu bir hata gibi görünüyor: bugs.chromium.org/p/v8/issues/detail?id=8561
Mathias Bynens

226

Diğer cevaplar ve yorumlar, iki döngü arasındaki farkın birincisinin ikincisinden bir yineleme gerçekleştirdiğini belirtmektedir. Bu doğrudur, ancak 25.000 öğeye kadar büyüyen bir dizide, bir yineleme az ya da çok küçük bir fark yaratacaktır. Bir basketbol sahası tahmini olarak, büyüdükçe ortalama uzunluğun 12.500 olduğunu varsayarsak, beklediğimiz fark 1 / 12.500 veya sadece% 0.008 olmalıdır.

Buradaki performans farkı, ekstra bir yinelemeyle açıklanamayacağından çok daha büyük ve sorun, sunumun sonuna doğru açıklanıyor.

this.primes bitişik bir dizidir (her öğenin bir değeri vardır) ve öğelerin tümü sayıdır.

Bir JavaScript motoru, böyle bir diziyi sayı içeren ancak başka değerler içerebilen veya değer içermeyen bir dizi nesne yerine basit bir gerçek sayılar dizisi olarak optimize edebilir. İlk biçime erişmek çok daha hızlıdır: daha az kod alır ve dizi çok daha küçük olduğundan önbelleğe daha iyi sığar. Ancak bu optimize edilmiş biçimin kullanılmasını engelleyebilecek bazı koşullar vardır.

Bir koşul, dizi öğelerinden bazıları eksikse olabilir. Örneğin:

let array = [];
a[0] = 10;
a[2] = 20;

Şimdi değeri a[1]nedir? Bu değeri yoktur . (Değerine sahip olduğunu söylemek doğru değildir undefined- değeri içeren undefinedbir dizi öğesi, eksik olan bir dizi öğesinden farklıdır.)

Bunu yalnızca rakamlarla göstermenin bir yolu yoktur, bu nedenle JavaScript motoru daha az optimize edilmiş biçimi kullanmak zorunda kalır. Eğer a[1], diğer iki benzer elemanları sayısal bir değerini içeren bir dizi potansiyel olarak sadece numaralar bir diziye optimize edilebilir.

Bir dizinin deoptimize edilmiş formata zorlanmasının bir başka nedeni, sunumda tartışıldığı gibi dizinin sınırları dışındaki bir öğeye erişmeye çalışmanız olabilir.

Dizinin <=sonunu geçen bir öğeyi okumaya çalışan ilk döngü . Son ekstra yinelemede algoritma hala düzgün çalışıyor:

  • this.primes[i]sonucunu undefinediçin idizi sonunun.
  • candidate % undefineddeğerini (herhangi bir değeri için candidate) değerlendirir NaN.
  • NaN == 0olarak değerlendirir false.
  • Bu nedenle, return trueyürütülmez.

Yani sanki ekstra yineleme hiç gerçekleşmemiş gibi - mantığın geri kalanı üzerinde hiçbir etkisi yoktur. Kod, ekstra yineleme olmadan olduğu gibi aynı sonucu üretir.

Ancak oraya ulaşmak için dizinin sonundan sonra var olmayan bir öğeyi okumaya çalıştı. Bu, diziyi optimizasyondan çıkarır - veya en azından bu konuşma sırasında yaptı.

İkinci döngü <yalnızca dizi içinde bulunan öğeleri okur, böylece optimize edilmiş bir dizi ve koda izin verir.

Sorun, konuşmanın 90-91 . Sayfalarında anlatılır .

Bu Google G / Ç sunumuna katıldım ve daha sonra konuşmacı (V8 yazarlarından biri) ile konuştum. Belirli bir durumu optimize etmek için yanlış bir (arka görüş) girişimi olarak bir dizinin sonuna okuma içeren kendi kodumu bir teknik kullanıyordum. Bir dizinin sonunu bile okumayı denerseniz, basit optimize edilmiş biçimin kullanılmasını önleyeceğini doğruladı .

V8 yazarının söylediği hala doğruysa, dizinin sonunu okumak, dizinin optimize edilmesini önler ve daha yavaş biçime geri dönmesi gerekir.

Bu arada V8'in bu durumu etkin bir şekilde ele almak için geliştirilmiş olması veya diğer JavaScript motorlarının farklı şekilde ele alması mümkündür. Ben şu ya da bu şekilde bilmiyorum, ama bu deoptimizasyon sunumun bahsettiği şeydi.


1
Dizi hala bitişik olduğundan eminim - bellek düzenini değiştirmek için bir neden yoktur. Bununla birlikte önemli olan, özellik erişimindeki sınır dışı dizin denetiminin optimize edilememesi ve bazen undefinedfarklı bir hesaplamaya yol açan bir sayı yerine kodun beslenmesidir .
Bergi

1
@Bergi Ben JS / V8 uzmanı değilim, ancak GC dillerindeki nesneler neredeyse her zaman gerçek nesnelere referanstır. Bu gerçek nesneler, başvurular bitişik olsa bile bağımsız tahsise sahiptir, çünkü GC nesnesi ömrü birbirine bağlı değildir. Optimize ediciler bu bağımsız tahsisleri bitişik olacak şekilde paketleyebilir, ancak (a) bellekte skyrockets kullanılır ve (b) biri yerine yinelenen iki ardışık blok (referanslar ve veriler) bulunur. Ben deli bir iyileştirici başvuruları serpiştirmek herhalde ve veri sevk ve bellek çizgili sahibi bir dizi var ...
Yakk - Adam Nevraumont

1
@Bergi Dizi, optimize edilmemiş durumda yine de bitişik olabilir, ancak dizi öğeleri optimize edilmiş durumda olduğu gibi aynı tipte değildir. Optimize edilmiş sürüm, ek bir kabartmadan basit bir sayı dizisidir. Optimize edilmemiş sürüm, dizideki Objectveri türlerinin herhangi bir karışımını desteklemesi gerektiğinden, bir nesne dizisidir (JavaScript değil, dahili bir nesne biçimi ). Yukarıda bahsettiğim gibi, beslenen döngüdeki kod undefinedalgoritmanın doğruluğunu etkilemez - hesaplamayı hiç değiştirmez (ekstra yineleme hiç olmamış gibi).
Michael Geary

3
@Bergi Bu konuşmayı yapan V8 yazarı, dizi sınırları dışında okuma girişiminin dizinin bir tür karışımına sahip gibi davranılmasına neden olduğunu söyledi: optimize edilmiş yalnızca sayı biçimi yerine diziyi tekrar optimize eder genel biçim. Optimize edilmiş durumda, bir C programında kullanabileceğiniz gibi basit bir sayı dizisidir. Optimize edilmiş durumda Value, herhangi bir türdeki değerlere referans tutabilen bir nesne dizisidir . (Adı oluşturdum Value, ancak nokta dizi öğelerinin sadece basit sayılar değil, sayıları veya diğer türleri saran nesneler olması.)
Michael Geary

3
V8 üzerinde çalışıyorum. Söz konusu dizi, ( kodun bu kısmı OP'de görünmese de) HOLEYkullanılarak oluşturulduğu için işaretlenir . diziler daha sonra doldurulsalar bile V8'de sonsuza kadar kalır . Bununla birlikte, dizili olan dizi, bu durumda mükemmel sorunun nedeni değildir; sadece her yinelemede ekstra bir Smi kontrolü yapmamız gerektiği anlamına gelir (deliklere karşı korunmak için), ki bu çok önemli değil. new Array(n)HOLEYHOLEY
Mathias Bynens

19

TL; DR Daha yavaş döngü, motoru daha az optimizasyonla veya daha az optimizasyonla yeniden derlemeye zorlayan VEYA fonksiyonu bu optimizasyonlardan herhangi biriyle derlememeye zorlayan Array 'sınırların dışında' erişime bağlıdır. (JIT-) Derleyicisi ilk derleme 'sürümünden önce bu durumu tespit etti / şüphelendiğinde), nedenini aşağıda okuyun;


Birisi sadece vardır bu (tamamen hayrete kimse zaten yaptım) demek:
OP'ın pasajı programlama kitabı anahat amaçlanan bir başlayanlar bir fiili örnek olacağını bir zaman olması için kullanılır / javascript 'diziler' endeksli bir başlangıç olduğunu vurgulamak 0'da değil, 1'de olduğu gibi yaygın bir 'yeni başlayanlar hatasına' bir örnek olarak kullanılabilir ('programlama hatası' ifadesinden nasıl kaçındığımı sevmezsiniz ;)): sınırların dışında Dizi erişimi .

Örnek 1:
Bir Dense Array(her zaman ES262 olarak) 0 tabanlı dizin kullanılarak 5 elemanları (aslında bitişik (indeksleri arasında boşlukları anlamına gelir) ve her bir dizin bir elemanı olan).

var arr_five_char=['a', 'b', 'c', 'd', 'e']; // arr_five_char.length === 5
//  indexes are:    0 ,  1 ,  2 ,  3 ,  4    // there is NO index number 5



Dolayısıyla, <vs <=(veya 'bir ekstra yineleme') arasındaki performans farkı hakkında gerçekten konuşmuyoruz , ancak şu şekilde konuşuyoruz:
'Doğru snippet (b) neden hatalı snippet (a)' dan daha hızlı çalışıyor? '

Cevap 2 kattır (ES262 dil uygulayıcının bakış açısından her ikisi de optimizasyon formlarıdır):

  1. Veri Gösterimi: Diziyi dahili olarak bellekte temsil etme / saklama (nesne, hashmap, 'gerçek' sayısal dizi, vb.)
  2. Fonksiyonel Makine kodu: bu 'Dizilere' erişen / işleyen kodun derlenmesi (okunması / değiştirilmesi)

Madde 1, kabul edilen cevapla yeterince (ve doğru IMHO) açıklanmıştır , ancak bu Madde 2: derlemeye sadece 2 kelime ('kod') harcar .

Daha doğrusu: JIT-Derleme ve daha da önemlisi JIT- RE -Derleme !

Dil belirtimi temel olarak sadece bir dizi algoritmanın tanımıdır ('tanımlı nihai sonuca ulaşmak için gerçekleştirilecek adımlar'). Bu, ortaya çıktığı gibi bir dili tanımlamanın çok güzel bir yoludur. Ve bir motorun uygulayıcılara açık olan belirli sonuçları elde etmek için kullandığı gerçek yöntemi terk ederek, tanımlı sonuçlar üretmek için daha verimli yollar bulma fırsatı sunar. Spesifikasyona uygun bir motor, tanımlanan herhangi bir giriş için spesifikasyonlu sonuçlar vermelidir.

Şimdi, javascript kodu / kitaplıkları / kullanımı arttıkça ve bir 'gerçek' derleyicinin ne kadar kaynak kullandığını (zaman / bellek / vb.) Hatırlayarak, bir web sayfasını ziyaret eden kullanıcıların bu kadar beklemesini sağlayamayacağımız açıktır (ve bunlara gereksinim duyuyoruz bu kadar çok kaynağa sahip olmak için).

Aşağıdaki basit işlevi düşünün:

function sum(arr){
  var r=0, i=0;
  for(;i<arr.length;) r+=arr[i++];
  return r;
}

Tamamen açık, değil mi? Herhangi bir ekstra açıklama gerektirmez, değil mi? Dönüş türü Number, değil mi?
Şey .. hayır, hayır ve hayır ... Adlandırılmış fonksiyon parametresine hangi argümanı ilettiğinize bağlı arr...

sum('abcde');   // String('0abcde')
sum([1,2,3]);   // Number(6)
sum([1,,3]);    // Number(NaN)
sum(['1',,3]);  // String('01undefined3')
sum([1,,'3']);  // String('NaN3')
sum([1,2,{valueOf:function(){return this.val}, val:6}]);  // Number(9)
var val=5; sum([1,2,{valueOf:function(){return val}}]);   // Number(8)

Sorunu görüyor musunuz? O zaman bunun büyük olası permütasyonları zar zor kazıdığını düşünün ... Yapana kadar RETURN işlevinin ne tür bir TİP olduğunu bilmiyoruz bile ...

Şimdi, aynı fonksiyon kodunun aslında tam anlamıyla (kaynak kodunda) açıklanan ve dinamik olarak program içinde üretilen 'diziler' gibi farklı tiplerde ve hatta giriş varyasyonlarında kullanıldığını hayal edin .

Bu nedenle, sumSADECE ONCE işlevini derleyecekseniz , her türlü girdi türü için her zaman spec tanımlı sonucu döndüren tek yol, daha sonra, sadece TÜM spec tarafından belirtilen ana VE alt adımları gerçekleştirerek spesifikasyona uygun sonuçları garanti edebilir (adsız bir y2k tarayıcısı gibi). Optimizasyon yok (çünkü varsayımlar yok) ve yavaş yavaş yorumlanmış komut dosyası dili kalıyor.

JIT-Compilation (Tam Zamanında olduğu gibi JIT) mevcut popüler çözümdür.

Böylece işlevi, yaptığı, döndürdüğü ve kabul ettiği varsayımları kullanarak derlemeye başlarsınız.
işlevin spesifik olmayan uygun olmayan sonuçlar döndürmeye başlayıp başlamayacağını tespit etmek için mümkün olduğunca basit kontroller yaparsınız (beklenmedik bir girdi aldığı için olduğu gibi). Ardından, önceki derlenmiş sonucu atın ve daha ayrıntılı bir şeye yeniden derleyin, zaten sahip olduğunuz kısmi sonuçla ne yapacağınıza karar verin (güvenilir olmak veya emin olmak için tekrar hesaplamak geçerli mi), işlevi programa geri bağlayın ve Tekrar deneyin. Nihayetinde spesifikasyondaki gibi adım adım senaryo yorumlamasına geri dönüyoruz.

Bütün bunlar zaman alır!

Tüm tarayıcılar motorlarında çalışır, her alt sürüm için her şeyin geliştiğini ve gerilediğini göreceksiniz. Dizeler tarihin bir noktasında gerçekten değişmez dizelerdi (bu nedenle array.join, dize birleşiminden daha hızlıydı), şimdi sorunu hafifleten halatlar (veya benzeri) kullanıyoruz. Her ikisi de spesifikasyona uygun sonuçlar verir ve önemli olan budur!

Uzun lafın kısası: javascript'in dilinin anlambiliminin sık sık sırtımızı alması (OP'nin örneğindeki bu sessiz hatada olduğu gibi), 'aptal' hataların derleyicinin hızlı makine kodu tükürme şansını artırdığı anlamına gelmez. 'Genellikle' doğru talimatları yazdığımızı varsayar: 'kullanıcılarımızın' (programlama dilinin) sahip olması gereken şu mantra: derleyiciye yardım et, ne istediğimizi tarif et, ortak deyimleri destekle (temel anlayış için asm.js'den ipuçları al) hangi tarayıcıları optimize etmeye çalışabilir ve neden).

Bu nedenle, performanstan bahsetmek hem önemlidir, ancak AYRICA bir mayın tarlası (ve söz konusu mayın tarlası nedeniyle gerçekten ilgili bazı materyalleri işaret etmek (ve alıntılamak) ile bitirmek istiyorum:

Varolmayan nesne özelliklerine ve sınırların dışındaki dizi öğelerine erişim, undefinedbir istisna oluşturmak yerine değeri döndürür . Bu dinamik özellikler JavaScript'te programlamayı kolaylaştırır, ancak JavaScript'in verimli makine koduna derlenmesini de zorlaştırır.

...

Etkili JIT optimizasyonu için önemli bir öneri, programcıların JavaScript'in dinamik özelliklerini sistematik bir şekilde kullanmasıdır. Örneğin, JIT derleyicileri, nesne özelliklerinin genellikle belirli bir sırada belirli bir türdeki bir nesneye eklendiğinden veya sınır dışı dizi erişimlerinin nadiren gerçekleşmesinden yararlanır. JIT derleyicileri, çalışma zamanında verimli makine kodu oluşturmak için bu düzenlilik varsayımlarından yararlanır. Bir kod bloğu varsayımları karşılarsa, JavaScript motoru verimli, oluşturulan makine kodunu yürütür. Aksi takdirde, motor daha yavaş koda veya programı yorumlamaya geri dönmelidir.

Kaynak:
"JITProf: JIT dostu olmayan JavaScript Kodunu saptamak"
Berkeley yayını, 2014, Liang Gong, Michael Pradel, Koushik Sen.
http://software-lab.org/publications/jitprof_tr_aug3_2014.pdf

ASM.JS (ayrıca dizi erişiminden de hoşlanmıyor):

Önceden Derleme

Asm.js, JavaScript'in katı bir alt kümesi olduğundan, bu belirtim yalnızca doğrulama mantığını tanımlar; yürütme semantiği yalnızca JavaScript'tir. Bununla birlikte, doğrulanmış asm.js, vaktinden önce (AOT) derlemeye uygundur. Dahası, bir AOT derleyicisi tarafından üretilen kod, aşağıdakileri içeren oldukça verimli olabilir:

  • tam sayıların ve kayan nokta sayılarının kutulanmamış gösterimleri;
  • çalışma zamanı türü denetimlerinin olmaması;
  • çöp toplama yokluğu; ve
  • Etkin yığın yükleri ve depoları (platforma göre değişen uygulama stratejileriyle).

Doğrulamayan kod, yorumlama ve / veya tam zamanında (JIT) derleme gibi geleneksel yollarla yürütmeye geri dönmelidir.

http://asmjs.org/spec/latest/

ve son olarak https://blogs.windows.com/msedgedev/2015/05/07/bringing-asm-js-to-chakra-microsoft-edge/ sınırları
kaldırırken motorun dahili performans iyileştirmeleri hakkında küçük bir alt bölüm vardı. kontrol edin (sadece sınırların kaldırılması-döngü dışında kontrolün zaten% 40'lık bir iyileşmesi vardı).



EDIT:
birden fazla kaynağın yorumlama için farklı JIT-Recompilation düzeyleri hakkında konuştuğunu unutmayın.

OP'nin pasajına ilişkin yukarıdaki bilgilere dayanan teorik örnek :

  • İsPrimeDivisible çağrısı
  • Genel varsayımlar (sınır dışı erişim yok gibi) kullanılarak isPrimeDivisible'ı derleyin
  • İşi yapmak
  • BAM, aniden dizi sınırların dışına erişir (en sonunda).
  • Crap, motor diyor ki, farklı (daha az) varsayımlar kullanarak bunuPrimeDivisible olarak yeniden derleyelim ve bu örnek motor, mevcut kısmi sonucu yeniden kullanıp kullanamayacağını anlamaya çalışmaz, bu yüzden
  • Yavaş fonksiyon kullanarak tüm işleri yeniden hesaplayın (umarım biter, aksi takdirde tekrarlayın ve bu kez sadece kodu yorumlayın).
  • Sonuç

Bu nedenle zaman:
İlk çalıştırma (sonunda başarısız oldu) + her bir yineleme için daha yavaş makine kodu kullanarak tekrar tekrar iş yapmak + yeniden derleme vb. Bu teorik örnekte açıkça> 2 kat daha uzun sürer !



DÜZENLEME 2: (feragatname: aşağıdaki gerçeklere dayalı varsayım)
Ne kadar çok düşünürsem, bu cevabın aslında bu snippet a (veya snippet b'deki performans bonusu) üzerindeki bu “ceza” nın daha baskın nedenini açıklayabileceğini düşünüyorum. , nasıl düşündüğünüze bağlı olarak), tam olarak neden bir programlama hatası (snippet a) deme konusunda kararlıyım:

this.primesBir 'yoğun dizi' saf sayısal olduğunu varsaymak oldukça cazip.

  • Kaynak koddaki sabit kodlu hazır bilgi ( derleme zamanından önce derleyici tarafından her şey zaten bilindiği için 'gerçek' bir dizi haline geldiği bilinen mükemmel aday ) VEYA
  • büyük olasılıkla new Array(/*size value*/), artan sıralı sırada ('gerçek' bir dizi olmak için bilinen bir başka uzun zamandır bilinen aday) önceden boyutlandırılmış ( ) bir sayısal işlev kullanılarak oluşturulur .

Biz de biliyoruz primesdizinin length olduğu önbelleğe olarak prime_count! (niyetini ve sabit boyutunu gösterir).

Ayrıca çoğu motorun başlangıçta Dizileri kopyala-değiştir (gerektiğinde) olarak geçirdiğini biliyoruz, bu da onları daha hızlı teslim etmenizi sağlıyor (değiştirmezseniz).

Bu nedenle, Array'ın primesbüyük olasılıkla dahili olarak zaten oluşturulduktan sonra değiştirilmeden optimize edilmiş bir dizi olduğunu varsaymak mantıklıdır (oluşturma işleminden sonra diziyi değiştiren kod yoksa derleyici için bilmek kolaydır) ve bu nedenle zaten (varsa) motor) optimize edilmiş bir şekilde depolanır, sanki bir a Typed Array.

sumİşlev örneğimi açıklığa kavuşturmaya çalıştığım gibi, iletilen argümanlar gerçekte ne olması gerektiğini ve bu kodun makine koduna nasıl derlendiğini etkiler. Bir geçme Stringiçin sumfonksiyonun işlevi JIT-Derleyen nasıl dize ancak değişiklik değiştirmemelisiniz! Bir Array öğesinin sumgeçirilmesi, makine kodunun farklı bir sürümünü (belki de bu tür için ek olarak veya dediği gibi 'şekil') derlemelidir.

primesDerleyici bu işlevin onu değiştirmeyeceğini bile bilirken, Typed_Array benzeri Diziyi anında bir şey_seçene dönüştürmek biraz güç gibi görünüyor !

2 seçenek bırakan bu varsayımlar altında:

  1. Sayı sınırlaması olmayan sayı olarak derleyin, sonunda sınır dışı sorunla karşılaşın, çalışmayı yeniden derleyin ve yineleyin (yukarıdaki düzenleme 1'de teorik örnekte belirtildiği gibi)
  2. Derleyici, önceden bağlanmış girişlerin dışında zaten algıladı (veya şüphelendi mi?) Ve işlev, geçen argüman, daha yavaş işlevsel makine koduyla sonuçlanan (daha fazla kontrol / dönüşüm / zorlama olacağı gibi) seyrek bir nesne gibi JIT-Derlendi vb.). Başka bir deyişle: işlev hiçbir zaman belirli optimizasyonlar için uygun değildi, sanki bir 'seyrek dizi' (- gibi) argümanı almış gibi derlendi.

Şimdi gerçekten bu 2 hangisinin olduğunu merak ediyorum!


2
Bazı temel konular hakkında iyi bir tartışma - ancak cevabı neredeyse hiç açıklamıyorsunuz (son cümlede). Belki en üstüne bir tl; dr ekleyin? ör. "Daha yavaş döngü, motoru optimizasyon olmadan döngüyü yeniden değerlendirmeye zorlayan sınır dizisini aşmaktan kaynaklanır. Nedenini öğrenmek için okumaya devam edin."
brichins

@brichins: teşekkürler ve ikinci ek düzenlememin ışığında biraz yeniden yazdığım öneri için teşekkürler, çünkü şimdi ne kadar çok düşünürsem, üstteki bu ifade de doğru görünüyor
GitaarLAB

6

Biraz bilimsellik katmak için, burada bir jsperf

https://jsperf.com/ints-values-in-out-of-array-bounds

Ints ile dolu bir dizinin kontrol durumunu test eder ve sınırlar içinde kalırken modüler aritmetik yaparak döngü yapar. 5 test vakası vardır:

  • 1. Sınırları aşmak
  • 2. Delikli diziler
  • 3. NaN'lere karşı modüler aritmetik
  • 4. Tamamen tanımlanmamış değerler
  • 5. Bir new Array()

İlk 4 vakanın performans açısından gerçekten kötü olduğunu gösteriyor. Sınırları aşmak diğer 3'ten biraz daha iyidir, ancak 4'ün tamamı en iyi durumdan kabaca% 98 daha yavaştır.
Durum new Array()neredeyse ham dizi kadar iyi, sadece yüzde birkaç daha yavaş.

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.