Performans: Javascript'te özyineleme ve yineleme


24

Son zamanlarda Javascript’in işlevsel yönleri ve Scheme ve Javascript arasındaki ilişki hakkında ilk makaleleri okudum (örneğin, http://dailyjs.com/2012/09/14/functional-programming/ ) OO yönleri prototip tabanlı bir dil olan Öz'den miras alınırken, işlevsel bir dildir).

Ancak benim sorum daha açık: Javascript'te yineleme ve yineleme performansı hakkında ölçütler olup olmadığını merak ediyordum.

Bazı dillerde (tasarım yinelemenin daha iyi performans gösterdiği yerlerde) farkın asgari düzeyde olduğunu biliyorum çünkü tercüman / derleyici özyinelemeyi yinelemeye dönüştürür, ancak sanırım bu en azından kısmen işlevsel olduğu için Javascript değil. dil.


3
Kendi testinizi yapın ve hemen jsperf.com
TehShrike

lütuf ve TCO'dan bahseden bir cevapla. ES6'nın TCO'yu belirttiği anlaşılıyor ama kangax.github.io/compat-table/es6 bir şeyini özlüyorum mu?
Matthias Kauer

Yanıtlar:


28

JavaScript kuyruk özyineleme optimizasyonu yapmaz , bu nedenle özyinelemeniz çok derinse, bir çağrı yığını taşması elde edebilirsiniz. Yinelemenin böyle bir sorunu yoktur. Çok fazla tekrarlanacağınızı düşünüyorsanız ve gerçekten yinelemeye ihtiyacınız varsa (örneğin, sel doldurma yapmak için), yinelemeyi kendi yığınınızla değiştirin.

Özyineleme performansı muhtemelen yineleme performansından daha kötüdür, çünkü işlev çağrıları ve geri dönüşleri durum koruması ve restorasyon gerektirirken, yineleme yalnızca bir işlevdeki başka bir noktaya atlar.


Merak ediyorum ... Boş bir dizinin yaratıldığı ve özyinelemeli işlev sitesinin dizinin içine bir pozisyonda atandığı ve daha sonra dizinin içine kaydedilen değerin döndürüldüğü bir kod parçası gördüm. "Özyineyi kendi yığınızla değiştir" derken bunu mu kastediyorsunuz? Örneğin: var stack = []; var factorial = function(n) { if(n === 0) { return 1 } else { stack[n-1] = n * factorial(n - 1); return stack[n-1]; } }
mastazi

@ mastazi: Hayır, bu dahili ile birlikte işe yaramaz bir çağrı yığını yapacaktır. Vikipedi sıraya dayalı sel doldurma gibi bir şey demek istedim .
Triang3l,

Bir dilin TCO yerine getirmediğine dikkat etmek önemlidir, ancak bir uygulama olabilir. İnsanların
JS'yi

1
@mastazi değiştirin elseo işlev else if (stack[n-1]) { return stack[n-1]; } elseve sahip Memoization . Faktör kodunu kim yazdıysa, tamamlanmamış bir uygulamaya sahipti (ve muhtemelen stack[n]yerine her yerde kullanmalıydı stack[n-1]).
Izkata

Teşekkürler @ İzkata, sık sık bu tür bir optimizasyon yapıyorum ama bugüne kadar ismini bilmiyordum. BT yerine CS
okumalıydım

20

Güncelleme : ES2015’ten bu yana JavaScript’in TCO’su var , bu nedenle aşağıdaki argümanın bir kısmı artık geçerli değil.


Javascript kuyruk çağrısı optimizasyonuna sahip olmasa da, özyineleme genellikle en iyi yoldur. Ve içtenlikle, son durumlar dışında, çağrı yığını taşmalarını almayacaksınız.

Performans akılda tutulması gereken bir şeydir, ancak erken optimizasyon da. Özyinelemenin yinelemeden daha zarif olduğunu düşünüyorsanız, o zaman devam edin. Görünüşe göre bu senin tıkanıklığın (ki hiç olmayacak), o zaman biraz çirkin iterasyonla değiştirebilirsin. Ancak, çoğu zaman, darboğaz DOM manipülasyonlarında ya da daha genel olarak I / O’dadır, kodun kendisinde değildir.

Özyineleme her zaman daha şık 1 .

1 : Kişisel görüş.


3
Özyinelemenin daha zarif olduğuna ve zarafetin okunabilirliğin yanı sıra sürdürülebilirliğin yanı sıra önemli olduğuna katılıyorum (bu özneldir, ancak bence özyinelemenin okunması çok kolaydır, bu nedenle bakımı kolaydır). Ancak, bazen performans önemlidir; Özyinelemenin performans açısından bile en iyi yol olduğu iddiasını destekleyebilir misiniz?
Mastazi

3
Cevabımda söylediğim gibi @mastazi, özyinelemenin senin darboğaz olacağından şüpheliyim. Çoğu zaman, bu DOM manipülasyonu veya daha genel olarak G / Ç'dir. Ve erken optimizasyonun tüm kötülüklerin kökü olduğunu unutmayın;)
Florian Margaine

DOM manipülasyonu için +1, çoğu zaman bir darboğaz oluyor! Bu konuda Yehuda Katz (Ember.js) ile ilgili çok ilginç bir röportajı hatırlıyorum.
Mastazi

1
@mike "Erken" tanımı " uygun zamandan önce olgun veya olgun" dur . Eğer varsa bilmek yinelemeli bir şey yapıyor bir stackoverflow neden olacağını, o zaman erken değil. Bununla birlikte, bir hevese (gerçek veri olmadan) sahip olursanız, o zaman erken olur.
Zirak

2
Javascript ile programın ne kadar yığınına sahip olacağını bilmiyorsunuz. IE6'da küçük bir yığın veya FireFox'ta büyük bir yığın olabilir. Özyinelemeli algoritmalar, Şema stili özyinelemeli döngü yapmadığınız sürece nadiren sabit bir derinliğe sahiptir. Sadece döngü tabanlı olmayan özyinelemenin erken optimizasyonlardan kaçınmaya uyduğu görülüyor.
mike30,

7

Javascript'te de bu performansı merak ediyordum, bu yüzden bazı deneyler yaptım (eski bir düğüm sürümünde de olsa). Yinelemelere karşı özyinelemeli bir hesap makinesi yazdım ve yerel olarak birkaç kez çalıştırdım. Sonuç, verginin (beklenen şekilde) tekrarlanmasına doğru oldukça eğimli görünüyordu.

Kod: https://github.com/j03m/trickyQuestions/blob/master/factorial.js

Result:
j03m-MacBook-Air:trickyQuestions j03m$ node factorial.js 
Time:557
Time:126
j03m-MacBook-Air:trickyQuestions j03m$ node factorial.js 
Time:519
Time:120
j03m-MacBook-Air:trickyQuestions j03m$ node factorial.js 
Time:541
Time:123
j03m-MacBook-Air:trickyQuestions j03m$ node --version
v0.8.22

Bunu deneyebilir "use strict";ve bir fark yaratıp yaratmadığını görebilirsiniz. ( jumpStandart arama dizisi yerine s üretecektir )
Burdock

1
Düğümün son sürümlerinde (6.9.1) oldukça benzer sonuçlar aldım. Özyinelemede bir miktar vergi var, ancak bunun son derece önemli bir durum olduğunu düşünüyorum - 1.000.000 döngü için 400ms'lik bir fark döngü başına. 1.000.000 döngü yapıyorsanız, akılda tutulması gereken bir şeydir.
Kelz

6

Gereğince OP tarafından talep Ben (umarım, kendimi aptal durumuna yapmadan: P) çip olacak

Sanırım hepimiz özyinelemenin sadece daha zarif bir kodlama yöntemi olduğuna karar verdik. İyi yapılırsa, daha fazla bakım yapılabilir kod yapabilir; bu da IMHO’nun 0.0001 ms’lik tıraş kadar önemlidir.

JS'nin Tail-call optimizasyonunu yapmadığı argümanına gelince, söz konusu: ECMA5'in katı modunun kullanılması TCO'yu mümkün kılıyor. Bir süre önce çok mutlu olmadığım bir şeydi ama en azından şimdi neden arguments.calleekatı modda hatalar attığını biliyorum . Yukarıdaki bağlantının bir hata raporuna bağlandığını biliyorum, ancak hata WONTFIX olarak ayarlandı. Ayrıca, standart TCO geliyor: ECMA6 (Aralık 2013).

İçgüdüsel olarak ve JS'nin işlevsel doğasına sadık kalarak, özyinelemenin zamanın% 99,99'unun daha verimli kodlama tarzı olduğunu söyleyebilirim. Ancak, Florian Margaine, darboğazın başka yerde bulunmasının muhtemel olduğunu söylerken bir noktaya değindi. DOM’da değişiklik yapıyorsanız, kodunuzu olabildiğince sürdürülebilir bir şekilde yazmaya odaklanmalısınız. DOM API budur: yavaş.

Daha hızlı olan seçenek olarak kesin bir cevap vermenin imkansız olduğunu düşünüyorum. Son zamanlarda, gördüğüm birçok jspref, Chrome'un V8 motorunun FF'in SpiderMonkey'inde 4x yavaş çalışan ve bunun tam tersi için 4x yavaş çalışan bazı işlerde gülünç derecede hızlı olduğunu gösteriyor . Modern JS motorları, kodunuzu optimize etmek için kolları üzerinde her türlü numaraya sahiptir. Uzman değilim, ama örneğin V8'in kapaklar (ve özyineleme) için oldukça optimize edilmiş olduğunu düşünüyorum, oysa ki MS'in JScript motoru değil. SpiderMonkey, DOM dahil olduğunda genellikle daha iyi performans gösterir ...

Kısacası: Hangi tekniğin daha iyi performans göstereceğini söyleyebilirim, her zamanki JS'de olduğu gibi, tahmin edilemez.


3

Sıkı mod olmadan, yineleme performansı genellikle yinelemeden biraz daha hızlıdır ( JIT'in daha fazla çalışmasını sağlamanın yanı sıra ). Kuyruk özyineleme optimizasyonu, tüm çağrı sırasını atlamaya çevirdiği için gözle görülür herhangi bir farkı ortadan kaldırır.

Örnek: Jsperf

Özyineleme ve yineleme arasında seçim yapmak söz konusu olduğunda kod netliği ve basitliği hakkında daha fazla endişe duymanızı öneririm.

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.