İşlevsel programlama okuyuculukta daha hızlı mı, çünkü farklı şeyler yazdığım için veya farklı şeyler derlendiğim için mi?


63

İşlevsel programlama dünyasına dalıyorum ve her yerde okumaya devam ediyorum; çok dilli / çok çekirdekli programlar için işlevsel dillerin daha iyi olduğu. Fonksiyonel dillerin özyineleme , rastgele sayılar vb. Gibi birçok şeyi farklı bir şekilde nasıl yaptığını anlıyorum , ancak çok okuyucunun işlevsel bir dilde daha hızlı olup olmadığını anlayamıyorum çünkü farklı bir şekilde derlenmiş veya farklı yazdığım için.

Örneğin, Java'da belirli bir protokolü uygulayan bir program yazdım. Bu protokolde iki taraf binlerce mesaj gönderip alır, bu mesajları şifreler ve tekrar tekrar gönderir (ve tekrar alır). Beklenildiği gibi, binlerce kullanımda çok okuyucunun anahtarıdır. Bu programda kilitleme söz konusu değildir .

Aynı programı Scala'da (JVM'yi kullanan) yazarsam bu uygulama daha hızlı olur mu? Evet ise neden? Yazma tarzı nedeniyle mi? O takdirde ise , çünkü yazı stili, şimdi Java lambda ifadeleri içerdiğini, ben lambda'da Java kullanarak aynı sonuçları elde edemedi? Yoksa Scala'nın işleri farklı şekilde derleyeceği için mi daha hızlı?


64
Afaik fonksiyonel programlama, çoklu okuyucuyu daha hızlı yapmaz. Çok okuyucunun uygulanması daha kolay ve daha güvenlidir, çünkü değişmezlik ve bu konuda yardımcı olacak yan etkileri olmayan işlevler gibi bazı fonksiyonel programlama özellikleri vardır.
Pieter B

7
O edilmektedir) 1) daha iyi gerçekten tanımlar 2 unutmayın kesinlikle değil sadece "hızlı" olarak tanımladı. Y'ye göre% 0.1 performans kazancı için kodun milyarlarca katını gerektiren bir X dili, makul herhangi bir iyi tanım için Y'den daha iyi değildir.
Bakuriu

2
"İşlevsel programlama" veya "işlevsel tarzda yazılmış programlar" hakkında mı sormak mı istediniz? Genellikle daha hızlı programlama daha hızlı bir program vermez.
Ben Voigt

1
Unutma, her zaman arka planda koşacak ve tahsisat taleplerini yerine getirecek bir GC var ... ve çok iş parçacığı olduğundan emin değilim ...
Mehrdad

4
Buradaki en basit cevap şudur: İşlevsel programlama, daha az yarış koşulu sorunlarını düşünecek programların yazılmasını sağlar , ancak zorunlu stilde yazılmış programların daha yavaş olacağı anlamına gelmez.
Dawid Pura

Yanıtlar:


97

İnsanların işlevsel dillerin paralel işleme için daha iyi olduğunu söylemelerinin nedeni, genellikle değişken durumlardan kaçınmalarıdır. Değişken durum, paralel işleme bağlamında "tüm kötülüklerin köküdür"; eşzamanlı süreçler arasında paylaşıldıklarında yarış koşullarına girmeyi gerçekten kolaylaştırıyorlar. Daha sonra yarış koşullarına çözüm, daha önce de belirttiğiniz gibi, çalışma zamanının genel olarak çalışmasına neden olan kilitleme ve senkronizasyon mekanizmalarını içerir, çünkü süreçler birbirlerinin paylaşılan kaynağı kullanmasını bekler; bu tür uygulamalar içinde derinden iç içe geçmiş.

Değişken durumdan kaçındığınızda, senkronizasyon ve kilitleme mekanizmalarına duyulan ihtiyaç bununla birlikte kaybolur. İşlevsel diller genellikle değişken durumlardan kaçındığından, doğal olarak paralel işleme için daha verimli ve etkilidir - paylaşılan kaynakların çalışma süresine sahip olmazsınız ve genellikle izleyen ek tasarım karmaşıklığına sahip olmazsınız.

Ancak, bunların hepsi tesadüfidir. Java'daki çözümünüz aynı zamanda değişebilir durumdan da kaçınırsa (özellikle iş parçacıkları arasında paylaşılır), Scala veya Clojure gibi işlevsel bir dile dönüştürülmesi, eşzamanlı verimlilik açısından herhangi bir fayda sağlamaz; kilitleme ve senkronizasyon mekanizmaları.

TL; DR: Scala'daki bir çözüm Java'da paralel işlemden daha verimliyse, kodun JVM'de derlenme veya çalıştırılma biçiminden değil, bunun yerine Java çözümünün iş parçacığı arasında değişken durum paylaşması nedeniyle değil, ya yarış koşullarına neden olabilir ya da bunlardan kaçınmak için senkronizasyon ek yükü ekleyebilir.


2
Yalnızca bir iş parçacığı bir veri parçasını değiştirirse; özel bir bakıma gerek yok. Sadece birden fazla iş parçacığı bir tür özel bakıma ihtiyacınız olan aynı verileri değiştirebiliyorsa (senkronizasyon, işlem belleği, kilitleme, her neyse). Bunun bir örneği, sürekli olarak fonksiyonel kodla mutasyona uğramış ancak çoklu ipliklerle değiştirilmemiş bir ipliğin yığınıdır.
Brendan,

31
Bir iş parçacığının verileri okurken mutasyona uğraması, "özel bakım" almaya başlamanız yeterlidir.
Peter Green

10
@Brendan: Hayır, eğer başka bir konu aynı veriyi okurken bir konu verileri değiştirirse, o zaman bir yarış şartınız olur. Sadece bir iplik değiştirilse bile özel dikkat gerekir.
Cornstalks

3
Değişken durum, paralel işleme bağlamında "kötülüğün köküdür" => henüz Rust'a bakmadıysanız, göz atmanızı öneririm. Gerçek sorunun takma ad ile karıştırılabilir olduğunu fark ederek mutasyona izin vermeyi çok etkili bir şekilde yönetir: yalnızca takma adınız varsa veya yalnızca değişkenlik varsa, hiçbir sorun yoktur.
Matthieu M.

2
@MatthieuM. Tamam, teşekkürler! Cevabımdaki şeyleri daha net ifade etmek için düzenleme yaptım. Değişken durum, eşzamanlı süreçler arasında paylaşıldığında yalnızca "tüm kötülüklerin köküdür" - Rust, mülkiyet kontrol mekanizmalarıyla kaçınılması gereken bir şey.
MichelHenrich

8

İkisinden de sırala. Daha hızlı çünkü kodunuzu daha hızlı derlenecek şekilde yazmak daha kolay. Dilleri değiştirerek mutlaka bir hız farkına sahip olmayacaksınız, ancak eğer işlevsel bir dille başlamış olsaydınız, çoklu okuyucuyu çok daha az programcı çabasıyla yapmış olabilirsiniz. Aynı satırlar boyunca, bir programcının zorunlu bir dilde hızı düşürecek diş açma hatalarını yapması çok daha kolay ve bu hataları fark etmesi çok daha zor.

Bunun nedeni, zorunlu programcılar genellikle tüm kilitsiz, iş parçacıklı kodları olabildiğince küçük bir kutuya koymaya ve en kısa sürede rahatça değiştirilebilir, senkronize dünyalarına geri getirmeye çalışmaktadırlar. Hız vermenize neden olan çoğu hata, bu sınır arabiriminde yapılır. İşlevsel bir programlama dilinde, bu sınır konusunda hata yapmaktan endişelenmenize gerek yoktur. Arama kodunuzun çoğu da "kutunun içinde" dır.


7

İşlevsel programlama, genel bir kural olarak, daha hızlı programlar için yapmaz. Yaptığı şey daha kolay paralel ve eşzamanlı programlama içindir. Bunun için iki ana anahtar var:

  1. Değişebilir durumdan kaçınılması, bir programda yanlış gidebilecek şeylerin sayısını ve daha fazlasını eşzamanlı bir programda azaltma eğilimindedir.
  2. Paylaşılan hafızanın ve kilit tabanlı senkronizasyon ilkellerinin daha üst düzey kavramlar lehine önlenmesi, kod konuları arasındaki senkronizasyonu basitleştirme eğilimindedir.

2 no'lu noktanın mükemmel bir örneği Haskell'de deterministik paralellik ve deterministik olmayan eşzamanlılık arasında açık bir ayrım olduğumuzdur . Simon Marlow'un Haskell'deki Mükemmel Paralel ve Eşzamanlı Programlama kitabından alıntı yapmaktan daha iyi bir açıklama yok (alıntılar Bölüm 1'den alınmıştır ):

Bir paralel program daha hızlı bir hesaplama yapmak için hesaplama donanımının (örneğin birkaç işlemci çekirdek) çok sayıda kullanan biridir. Amaç, hesaplamanın farklı bölümlerini aynı anda çalışan farklı işlemcilere devrederek daha erken bir cevaba ulaşmaktır.

Buna karşılık, eşzamanlılık , birden fazla denetim dizisinin olduğu bir program yapılandırma tekniğidir. Kavramsal olarak, kontrol iplikleri “aynı anda” yürütülür; yani kullanıcı etkilerini araya eklenmiş olarak görür. Aslında aynı anda mı çalıştıkları ya da uygulanmadıkları bir uygulama detayıdır; eşzamanlı bir program tek bir işlemcide bir araya getirilmiş işlem veya birden çok fiziksel işlemcide gerçekleştirilebilir.

Buna ek olarak, Marlow da determinizmin boyutunu ortaya çıkarır :

İlişkili bir ayrım deterministik ve klasik olmayan programlama modelleri arasındadır. Deterministik bir programlama modeli, her programın sadece bir sonuç verebileceği bir modeldir; oysa, klasik olmayan bir programlama modeli, uygulamanın bir yönüne bağlı olarak farklı sonuçlara sahip olan programları kabul eder. Eşzamanlı programlama modelleri mutlaka belirleyici değildir, çünkü öngörülemeyen zamanlarda olaylara neden olan dış ajanlarla etkileşime girmeleri gerekir. Nondeterminizm bazı kayda değer dezavantajlara sahiptir, ancak: Programların test edilmesi ve bununla ilgili sebepler oldukça zorlaşır.

Paralel programlama için mümkünse deterministik programlama modellerini kullanmak istiyoruz. Amaç sadece cevaba daha hızlı bir şekilde ulaşmak olduğundan, programımızı süreçte hata ayıklamayı zorlaştırmayacağız. Deterministik paralel programlama her iki dünyanın da en iyisidir: Ardışık programda test etme, hata ayıklama ve mantık yürütme yapılabilir, ancak program daha fazla işlemci eklenerek daha hızlı çalışır.

Haskell'de paralellik ve eşzamanlılık özellikleri bu kavramlar etrafında tasarlanır. Özellikle, hangi dillerin bir özellik seti olarak bir araya geldiğini, Haskell ikiye ayırır:

  • Paralellik için deterministik özellikler ve kütüphaneler .
  • Deterministik olmayan özellikler ve eşzamanlılık için kütüphaneler .

Yalnızca saf ve deterministik bir hesaplamayı hızlandırmaya çalışıyorsanız, deterministik paralelliğe sahip olmak işleri çok daha kolaylaştırır. Genellikle böyle bir şey yaparsınız:

  1. Her biri hesaplaması pahalı olan ancak birbirlerine çok fazla bağlı olmayan yanıtların listesini üreten bir işlev yazın. Bu Haskell, bu yüzden listeler tembeldir - öğelerinin değerleri aslında bir tüketici talep edene kadar hesaplanmaz.
  2. İşlevinizin sonuç listelerinin öğelerini birden fazla çekirdeğe paralel olarak tüketmek için Stratejiler kitaplığını kullanın .

Aslında bunu birkaç hafta önce oyuncak proje programlarımdan biriyle yaptım . Programı paralel hale getirmek çok önemliydi; yapmam gereken en önemli şey, aslında "bu listenin öğelerini paralel olarak hesapla" diyen bir kod eklemek (satır 90), ve daha pahalı test durumlarımdan bazıları.

Programım, geleneksel kilit tabanlı çok iş parçacığı hizmet programlarına girdiğimden daha mı hızlı? Ben çok şüpheliyim. Benim durumumdaki temiz şey çok az para kazanmaktan çok etkileniyordu - kodum muhtemelen çok yetersizdi, ancak paralelleştirmek çok kolay çünkü düzgün bir şekilde profillemekten ve optimize etmekten çok daha az bir çaba ile büyük bir hız kazandım. ve yarış koşulları riski yoktur. Ve bence, işlevsel programlamanın “daha ​​hızlı” programlar yazmanıza izin verdiği ana yoldur.


2

Haskell'de, bir modifikasyon kütüphanesi vasıtasıyla özel değiştirilebilir değişkenler almadan modifikasyon gerçekten mümkün değildir. Bunun yerine, işlevler değerleri ile aynı anda ihtiyaç duydukları değişkenleri oluşturur (bunlar tembel olarak hesaplanır) ve artık gerekmediğinde toplanan çöpler.

Değişiklik değişkenlerine ihtiyacınız olsa bile, genellikle seyrek ve değiştirilemeyen değişkenlerle birlikte kullanabilirsiniz. (Haskell'deki bir başka güzel şey, kilitleri atomik işlemlerle değiştiren STM'dir, ancak bunun sadece işlevsel programlama için olup olmadığından emin değilim.) Genellikle, programın yalnızca bir kısmının işleri iyileştirmek için paralel yapılması gerekir. epeyce.

Bu, Haskell'deki paralelliğin çoğunu kolaylaştırıyor ve aslında otomatik hale getirme çabaları sürüyor. Basit kod için, paralellik ve mantık ayrılabilir.

Ayrıca, Haskell'de değerlendirme sırasının önemli olmaması nedeniyle, derleyici sadece değerlendirilmesi gereken bir sıra yaratır ve bunları mümkün olan her çekirdeğe gönderir; aslında gerekli olana kadar konu olur. Değerlendirme sırası önemli değil, genellikle fonksiyonel programlama gerektiren saflık özelliğidir.

Daha fazla Okuma
Haskell Paralellik (HaskellWiki)
"Gerçek Dünya Haskell" in Eşzamanlı ve çok çekirdekli Programlama
Simon Marlow tarafından Haskell Paralel ve Eşzamanlı Programlama


7
grep java this_post. grep scala this_postve grep jvm this_postsonuç bulunamadı :)
Andres F.

4
Soru belirsiz. Başlıkta ve birinci paragrafta, genel olarak işlevsel programlama hakkında , ikinci ve üçüncü paragrafta özellikle Java ve Scala hakkında bilgi alır . Bu talihsiz bir durumdur, çünkü özellikle Scala'nın en güçlü yanlarından biri tam da işlevsel bir dil olmadığı gerçeğidir . Martin Odersky buna "post-fonksiyonel", diğerleri "nesne" olarak adlandırıyor. "İşlevsel programlama" teriminin iki farklı tanımı vardır. Biri "birinci sınıf prosedürlerle programlama" dır (LISP'ye uygulanan orijinal tanım), diğeri…
Jörg W Mittag

2
"referans olarak saydam, saf, yan etki göstermeyen fonksiyonlar ve değişmez kalıcı verilerle programlama" (çok daha katı ve aynı zamanda daha yeni yorumlarla). Bu cevap, anlam ifade eden ikinci yorumu ele almaktadır, çünkü a) ilk yorum, paralellik ve eşzamanlılık ile tamamen ilişkili değildir, b) ilk yorum, mütevazi geniş kullanımda bile hemen hemen her dil hariç, temel olarak anlamsız hale gelmiştir. Günümüzde birinci sınıf prosedürler (Java dahil) var ve c) OP, Java ve Scala arasındaki farkı soruyor, fakat hayır yok…
Jörg W Mittag

2
ikisi arasında tanım # 1 ile sadece tanım # 2.
Jörg W Mittag

Değerlendirme konusu, burada yazıldığı gibi doğru değildir; Varsayılan olarak, çalışma zamanı hiç çoklu okuma kullanmaz ve IIRC çoklu okuma özelliğini etkinleştirmiş olsanız bile, çalışma zamanına paralel olarak ne yapması gerektiğini söylemeniz gerekir.
Kübik
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.