Anonim işlevlerin kullanılması performansı etkiler mi?


89

Javascript'te adlandırılmış işlevler ile anonim işlevler arasında performans farkı var mı?

for (var i = 0; i < 1000; ++i) {
    myObjects[i].onMyEvent = function() {
        // do something
    };
}

vs

function myEventHandler() {
    // do something
}

for (var i = 0; i < 1000; ++i) {
    myObjects[i].onMyEvent = myEventHandler;
}

Birincisi, kodunuzu nadiren kullanılan işlevlerle karıştırmadığı için daha derli topludur, ancak bu işlevi birden çok kez yeniden beyan etmeniz önemli mi?


Sorunun içinde olmadığını biliyorum, ancak kod temizliği / okunabilirliği açısından 'doğru yol'un ortada bir yerde olduğunu düşünüyorum. Nadiren kullanılan üst düzey işlevlerin "karmaşası" can sıkıcıdır, ancak yoğun şekilde iç içe geçmiş kod da, çağrılmalarıyla aynı hizada bildirilen anonim işlevlere çok bağlıdır (node.js geri arama cehennemini düşünün). Hem eski hem de ikincisi hata ayıklama / yürütme izlemeyi zorlaştırabilir.
Zac B

Aşağıdaki performans testleri, işlevi binlerce yineleme için çalıştırır. Önemli bir fark görseniz bile, kullanım durumlarının çoğu, bu sıranın yinelemelerinde bunu yapmayacaktır. Bu nedenle, ihtiyaçlarınıza uygun olanı seçmek ve bu özel durum için performansı göz ardı etmek daha iyidir.
kullanıcı

@nickf elbette çok eski bir soru, ancak yeni güncellenmiş yanıtı görün
Chandan Pasunoori

Yanıtlar:


89

Buradaki performans sorunu, anonim bir işlev kullanmanız değil, döngünün her yinelemesinde yeni bir işlev nesnesi oluşturmanın maliyetidir:

for (var i = 0; i < 1000; ++i) {    
    myObjects[i].onMyEvent = function() {
        // do something    
    };
}

Aynı kod gövdesine sahip olsalar ve sözcük kapsamına ( kapanış ) hiçbir bağlayıcılığı olmasalar bile, bin farklı işlev nesnesi oluşturuyorsunuz . Öte yandan, aşağıdakiler daha hızlı görünür, çünkü döngü boyunca dizi öğelerine aynı işlev referansını atar :

function myEventHandler() {
    // do something
}

for (var i = 0; i < 1000; ++i) {
    myObjects[i].onMyEvent = myEventHandler;
}

Döngüye girmeden önce anonim işlevi oluşturacak olsaydınız, o zaman yalnızca dizi öğelerine döngünün içindeyken başvurular atarsanız, adlandırılmış işlev sürümüyle karşılaştırıldığında herhangi bir performans veya anlamsal fark olmadığını göreceksiniz:

var handler = function() {
    // do something    
};
for (var i = 0; i < 1000; ++i) {    
    myObjects[i].onMyEvent = handler;
}

Kısacası, adlandırılmış işlevler yerine anonim kullanmanın gözlemlenebilir bir performans maliyeti yoktur.

Bir kenara, yukarıdan aşağıdakiler arasında hiçbir fark olmadığı görünebilir:

function myEventHandler() { /* ... */ }

ve:

var myEventHandler = function() { /* ... */ }

İlki bir işlev bildirimi iken, ikincisi anonim bir işleve değişken atamadır. Aynı etkiye sahip gibi görünseler de, JavaScript onlara biraz farklı davranır. Farkı anlamak için, " JavaScript fonksiyon beyanı belirsizliği " ni okumanızı tavsiye ederim .

Herhangi bir yaklaşım için gerçek yürütme süresi, büyük ölçüde tarayıcının derleyici ve çalışma zamanı uygulaması tarafından belirlenecektir. Modern tarayıcı performansının eksiksiz bir karşılaştırması için JS Perf sitesini ziyaret edin


İşlev gövdesinin önündeki parantezleri unuttunuz. Sadece test ettim, gerekli.
Chinoto Vokro

Görünüşe göre kıyaslama sonuçları çok js-motor bağımlı!
aleclofabbro

3
JS Perf örneğinde bir kusur yok mu: Durum 1 yalnızca işlevi tanımlar , halbuki durum 2 ve 3 yanlışlıkla işlevi çağırır .
bluenote10

Öyleyse bu mantığı kullanmak, node.jsweb uygulamaları geliştirirken , anonim geri aramalar oluşturmaktansa, istek akışının dışında işlevler oluşturmanın ve bunları geri arama olarak iletmenin daha iyi olduğu anlamına mı geliyor?
Xavier T Mukodi

23

İşte test kodum:

var dummyVar;
function test1() {
    for (var i = 0; i < 1000000; ++i) {
        dummyVar = myFunc;
    }
}

function test2() {
    for (var i = 0; i < 1000000; ++i) {
        dummyVar = function() {
            var x = 0;
            x++;
        };
    }
}

function myFunc() {
    var x = 0;
    x++;
}

document.onclick = function() {
    var start = new Date();
    test1();
    var mid = new Date();
    test2();
    var end = new Date();
    alert ("Test 1: " + (mid - start) + "\n Test 2: " + (end - mid));
}

Sonuçlar:
Test 1: 142ms Test 2: 1983ms

Görünüşe göre JS motoru Test2'de aynı işlev olduğunu fark etmiyor ve her seferinde derliyor.


3
Bu test hangi tarayıcıda yapıldı?
andynil

5
Benim için Chrome 23: (2ms / 17ms), IE9: (20ms / 83ms), FF 17: (2ms / 96ms)
Davy8

Cevabınız daha fazla ağırlığı hak ediyor. Intel i5 4570S'deki zamanlarım: Chrome 41 (1/9), IE11 (1/25), FF36 (1/14). Açıkça bir döngüdeki anonim işlev daha kötü performans gösterir.
ThisClark

3
Bu test göründüğü kadar kullanışlı değil. Her iki örnekte de, aslında icra edilen iç işlev yoktur. Etkili bir şekilde tüm bu testler, 10000000 kez bir işlev oluşturmanın, bir işlev oluşturmaktan daha hızlı olduğudur.
Nükleon

2

Genel bir tasarım ilkesi olarak, aynı kodu birden çok kez uygulamaktan kaçınmalısınız. Bunun yerine, ortak kodu bir işleve kaldırmalı ve bu (genel, iyi test edilmiş, değiştirmesi kolay) işlevi birden çok yerden çalıştırmalısınız.

Eğer (sorunuzdan çıkardığınız şeyin aksine) dahili işlevi bir kez açıklıyorsanız ve bu kodu bir kez kullanıyorsanız (ve programınızda benzer hiçbir şey yoksa) o zaman muhtemelen anonomik bir işlev (bu bir tahmin millet) tarafından aynı şekilde ele alınır. normal adlandırılmış bir işlev olarak derleyici.

Belirli durumlarda çok kullanışlı bir özelliktir, ancak birçok durumda kullanılmamalıdır.


1

Çok fazla fark beklemem ama eğer varsa, muhtemelen komut dosyası motoruna veya tarayıcıya göre değişecektir.

Kodu çalmayı daha kolay bulursanız, işlevi milyonlarca kez çağırmayı beklemediğiniz sürece performans sorun teşkil etmez.


1

Performans üzerinde bir etkiye sahip olabileceğimiz yer, işlev bildirme işlemidir. İşte başka bir işlevin bağlamı içinde veya dışında işlev bildirmenin bir ölçütü:

http://jsperf.com/function-context-benchmark

Chrome'da, işlevi dışarıda ilan edersek işlem daha hızlıdır, ancak Firefox'ta tam tersi olur.

Diğer bir örnekte, iç işlev saf bir işlev değilse, Firefox'ta da performans eksikliği yaşayacağını görüyoruz: http://jsperf.com/function-context-benchmark-3


0

Döngünüzü çeşitli tarayıcılarda, özellikle de IE tarayıcılarında kesinlikle daha hızlı hale getirecek olan şey, aşağıdaki gibi döngüdür:

for (var i = 0, iLength = imgs.length; i < iLength; i++)
{
   // do something
}

Döngü durumuna rastgele bir 1000 koydunuz, ancak dizideki tüm öğelerin üzerinden geçmek isterseniz kaymamı alırsınız.


0

bir referans neredeyse her zaman bahsettiği şeyden daha yavaş olacaktır. Şöyle düşünün - diyelim ki 1 + 1'in sonucunu yazdırmak istiyorsunuz. Bu daha mantıklı:

alert(1 + 1);

veya

a = 1;
b = 1;
alert(a + b);

Bunun ona bakmanın gerçekten basit bir yolu olduğunun farkındayım, ama açıklayıcı, değil mi? Bir referansı yalnızca birden çok kez kullanılacaksa kullanın - örneğin, bu örneklerden hangisi daha mantıklıdır:

$(a.button1).click(function(){alert('you clicked ' + this);});
$(a.button2).click(function(){alert('you clicked ' + this);});

veya

function buttonClickHandler(){alert('you clicked ' + this);}
$(a.button1).click(buttonClickHandler);
$(a.button2).click(buttonClickHandler);

İkincisi, daha fazla satıra sahip olsa bile daha iyi pratiktir. Umarım tüm bunlar yardımcı olur. (ve jquery sözdizimi kimseyi atmadı)


0

@kafadergisi

(Keşke sadece yorum yapacak bir temsilcim olsaydı, ama bu siteyi daha yeni buldum)

Demek istediğim, burada adlandırılmış / anonim işlevler ile bir yinelemede çalıştırma + derleme kullanım durumu arasında bir karışıklık var. Gösterdiğim gibi, anon + isimli arasındaki fark kendi içinde ihmal edilebilir - bunun hatalı olan kullanım durumu olduğunu söylüyorum.

Bana açık görünüyor, ancak değilse, en iyi tavsiyenin "aptalca şeyler yapma" olduğunu düşünüyorum (bu kullanım senaryosunun sürekli blok kaydırma + nesne oluşturma bir tanesidir) ve emin değilseniz, test edin!



0

Anonim nesneler, adlandırılmış nesnelerden daha hızlıdır. Ancak daha fazla işlevi çağırmak daha pahalıdır ve anonim işlevleri kullanmaktan elde edebileceğiniz tasarrufları gölgede bırakacak şekilde. Çağrılan her işlev, küçük ama önemsiz olmayan bir ek yük getiren çağrı yığınına eklenir.

Ancak, şifreleme / şifre çözme rutinleri veya performansa benzer şekilde hassas bir şey yazmıyorsanız, diğerlerinin de belirttiği gibi, hızlı kod yerine zarif, okunması kolay kod için optimize etmenin her zaman daha iyidir.

İyi tasarlanmış bir kod yazdığınızı varsayarsak, o zaman hız sorunları yorumlayıcıları / derleyicileri yazanların sorumluluğunda olmalıdır.


0

@kafadergisi

Bu oldukça saçma bir test, uygulama ve derlemeyi karşılaştırıyorsunuz oradaki süresini bu da açıkça yöntem 1'e (N kez derler, JS motoruna bağlı olarak) yöntem 2'ye (bir kez derlenir) mal olacak. Deneme süresi yazma kodunu böyle bir şekilde geçirecek bir JS geliştiricisi hayal edemiyorum.

Çok daha gerçekçi bir yaklaşım, aslında document.onclick yöntemi için kullandığınız gibi, anonim atamadır.

Sizinkine benzer bir test çerçevesi kullanmak:


function test(m)
{
    for (var i = 0; i < 1000000; ++i) 
    {
        m();
    }
}

function named() {var x = 0; x++;}

var test1 = named;

var test2 = function() {var x = 0; x++;}

document.onclick = function() {
    var start = new Date();
    test(test1);
    var mid = new Date();
    test(test2);
    var end = new Date();
    alert ("Test 1: " + (mid - start) + "ms\n Test 2: " + (end - mid) + "ms");
}

0

Yorumlarda belirtildiği gibi @ nickf cevabının cevabı:

Milyonlarca kez oluşturmaktan daha hızlı bir işlev oluşturmaktır

basitçe evet. Ancak JS performansının gösterdiği gibi, bir milyon kat daha yavaş değil, bu da aslında zamanla hızlandığını gösteriyor.

Benim için daha ilginç soru şu:

Tekrarlanan oluşturma + çalıştırma , bir kez + tekrarlanan çalıştırma ile nasıl karşılaştırılır ?

Bir işlev karmaşık bir hesaplama yaparsa, işlev nesnesini oluşturma süresi büyük olasılıkla ihmal edilebilir. Ama üzerinde kafa ne hakkında yaratmak durumlarda koşmak hızlı? Örneğin:

// Variant 1: create once
function adder(a, b) {
  return a + b;
}
for (var i = 0; i < 100000; ++i) {
  var x = adder(412, 123);
}

// Variant 2: repeated creation via function statement
for (var i = 0; i < 100000; ++i) {
  function adder(a, b) {
    return a + b;
  }
  var x = adder(412, 123);
}

// Variant 3: repeated creation via function expression
for (var i = 0; i < 100000; ++i) {
  var x = (function(a, b) { return a + b; })(412, 123);
}

Bu JS Perf , işlevi yalnızca bir kez oluşturmanın beklendiği gibi daha hızlı olduğunu gösterir. Bununla birlikte, basit bir ekleme gibi çok hızlı bir işlemle bile, işlevi tekrar tekrar oluşturmanın ek yükü yalnızca yüzde birkaçdır.

Fark muhtemelen sadece işlev nesnesini oluşturmanın karmaşık olduğu ve ihmal edilebilir bir çalışma süresini korurken, örneğin tüm işlev gövdesi bir if (unlikelyCondition) { ... }.

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.