Haskell (GHC) neden bu kadar hızlı?


247

Haskell ( GHCderleyici ile) beklediğinizden çok daha hızlı . Doğru kullanıldığında, düşük seviyeli dillere yakın olabilir. (Haskellers'ın en sevdiği şey, C'nin% 5'ine girmeyi denemek ve (veya onu yenmek), ancak GHC Haskell'i C'yi derlediğinden, verimsiz bir C programı kullandığınız anlamına gelir.) Sorum şu, neden?

Haskell açıklayıcı ve lambda analizine dayanmaktadır. Makine mimarileri kabaca turing makinelerine dayanarak açıkça zorunludur. Gerçekten de, Haskell'in belirli bir değerlendirme sırası bile yok. Ayrıca, makine veri türleriyle uğraşmak yerine, her zaman cebirsel veri türlerini yaparsınız.

En garip, daha üst düzey fonksiyonlardır. Fonksiyonları anında oluşturmanın ve onları atmanın bir programı yavaşlatacağını düşünürdünüz. Ancak daha üst düzey işlevlerin kullanılması Haskell'i daha hızlı hale getirir. Gerçekten de, Haskell kodunu optimize etmek için, daha makine benzeri değil, daha zarif ve soyut hale getirmeniz gerekiyor gibi görünüyor. Haskell'in daha gelişmiş özelliklerinin hiçbiri, geliştirmezlerse performansını bile etkilemez .

Bu kulağa sağlam geliyorsa üzgünüm, ama işte benim sorum: Haskell (GHC ile derlenmiş) neden soyut doğası ve fiziksel makinelerden farklılıkları göz önüne alındığında bu kadar hızlı?

Not: C ve diğer zorunlu dillerin Turing Makineleri'ne biraz benzemesinin sebebi (ancak Haskell'in Lambda Calculus'a benzediği ölçüde değil), zorunlu bir dilde, sınırlı sayıda duruma (akarak hattı numarası) sahip olmanızdır. , bir Bant (koç) ile birlikte, durum ve mevcut bant banda ne yapılacağını belirler. Turing Makinelerinden bilgisayarlara geçiş için Wikipedia girişine, Turing makinesi eşdeğerlerine bakın .


27
"GHC Haskell'i C'yi derlediğinden" değil. GHC'nin birden fazla arka ucu vardır. En eskisi (ancak varsayılan değil) bir C üreticisidir. IR için Cmm kodu oluşturur, ancak bu normalde beklediğiniz "C'ye derleniyor" değildir. ( downloads.haskell.org/~ghc/latest/docs/html/users_guide/… )
viraptor

20
Simon Payton Jones'un (GHC'nin birincil uygulayıcısı) Fonksiyonel Programlama Dillerinin Uygulanmasını okumanızı şiddetle tavsiye ederim , birçok soruya cevap verecektir.
Joe Hillenbrand

94
Neden? 25 yıllık sıkı çalışma.
augustss

31
Diyerek şöyle devam etti: "Buna gerçek bir cevap olsa da, fikir istemekten başka bir şey yapmaz." - Bu bir soruyu kapatmak için olabilecek en kötü nedendir. O Çünkü edebilir iyi bir cevabım var ama potansiyel olarak da düşük kaliteli olanları çekecek. Yuck! Akademik araştırmalar ve bazı gelişmeler olduğunda, iyi, tarihi, olgusal bir cevap aldım. Ama gönderemiyorum çünkü insanlar bu sorunun düşük kaliteli cevapları da çekebileceğinden endişe duyuyorlar. Yine yuck.
sclv

7
@cimmanon İşlevsel bir derleyicinin nasıl çalıştığının ayrıntılarını gözden geçirmek için bir ay veya birkaç blog gönderisine ihtiyacım var . Bir grafik makinesinin stok donanımına nasıl temiz bir şekilde uygulanabileceğini ve daha fazla okumak için ilgili kaynaklara işaret edebildiğini özetlemek için SO cevabına ihtiyacım var ...
sclv

Yanıtlar:


264

Dietrich Epp ile aynı fikirdeyim: GHC'yi hızlı yapan birkaç şeyin birleşimi .

Her şeyden önce Haskell çok üst düzey. Bu, derleyicinin kodunuzu bozmadan agresif optimizasyonlar yapmasını sağlar .

SQL'i düşünün. Şimdi, bir SELECTifade yazdığımda , zorunlu bir döngü gibi görünebilir , ama değil . Bu olabilir gibi bakmak belirtilen koşullara uygun olan birini bulmaya çalışırken o tablodaki tüm satırların üzerine döngüler, ancak aslında tamamen farklı performans özelliklerine sahip - "derleyici" (DB motoru) araması yerine bir dizin yapıyor olabilir. Ancak SQL çok yüksek düzeyde olduğundan, "derleyici" tamamen farklı algoritmaların yerine geçebilir, birden çok işlemci veya G / Ç kanalı veya tüm sunucuları şeffaf bir şekilde uygulayabilir .

Haskell'i aynı olarak düşünüyorum. Sen belki düşünmek sadece, ikinci listeye girdi Harita listesine üçüncü listeye ikinci listeye filtre ve sonra sonuçlandı kaç öğe saymak Haskell istedi. Ancak GHC'nin perde arkasındaki akış-füzyon yeniden yazma kurallarını uyguladığını görmediniz, her şeyi tek bir dar makine kod döngüsüne dönüştürerek tüm işi, tahsis olmaksızın verilerin üzerinden tek bir geçişte gerçekleştirecek - bu tür bir şey el ile yazmak için sıkıcı, hataya yatkın ve bakım gerektirmeyen olmak. Bu sadece koddaki düşük düzeyli ayrıntıların olmaması nedeniyle gerçekten mümkündür.

Buna bakmanın bir başka yolu da olabilir… Haskell neden hızlı olmasın ? Onu yavaşlatan ne yapar?

Perl veya JavaScript gibi yorumlanmış bir dil değil. Java veya C # gibi bir sanal makine sistemi bile değil. Tamamen yerel makine koduna kadar derler, böylece orada ek yük yoktur.

OO dillerinin aksine [Java, C #, JavaScript…], Haskell tam tip silme özelliğine sahiptir [C, C ++, Pascal…]. Tüm tür denetimi yalnızca derleme zamanında gerçekleşir. Yani sizi yavaşlatmak için çalışma zamanı türü denetimi yok. (Bu konuda boş-işaretçi kontrolü yoktur. Örneğin Java'da JVM, boş işaretçiler olup olmadığını kontrol etmeli ve birini iptal ederseniz bir istisna atmalıdır. Haskell bu denetimle uğraşmak zorunda değildir.)

"Çalışma anında anında fonksiyonlar yaratmanın" yavaş geldiğini söylüyorsunuz, ancak çok dikkatli bakarsanız, aslında bunu yapmazsınız. Göründüğün gibi görünebilir , ama yapmazsın. Eğer derseniz (+5)kaynak kodunuzda kodlanmıştır. Çalışma zamanında değişemez. Yani bu gerçekten dinamik bir işlev değil. Kavisli fonksiyonlar bile parametreleri sadece bir veri bloğuna kaydediyor. Yürütülebilir kodun tamamı derleme zamanında mevcuttur; çalışma zamanı yorumu yoktur. ("Eval işlevi" olan diğer dillerden farklı olarak.)

Pascal'ı düşün. Eski ve kimse artık bunu gerçekten kullanmıyor, ancak kimse Pascal'ın yavaş olduğundan şikayet etmiyordu . Bu konuda hoşlanmayacak çok şey var, ama yavaşlık aslında onlardan biri değil. Haskell, elle bellek yönetimi yerine çöp toplama dışında, Pascal'dan farklı bir şey yapmıyor. Ve değişmez veriler, GC motorunda [tembel değerlendirme biraz karmaşıklaşır] birkaç optimizasyona izin verir.

Sanırım Haskell gelişmiş ve sofistike ve üst düzey görünüyor ve herkes "oh vay, bu gerçekten güçlü, inanılmaz derecede yavaş olmalı! " Diye düşünüyor. Ama öyle değil. Ya da en azından beklediğiniz gibi değil. Evet, inanılmaz bir tip sistemi var. Ama biliyor musun? Bütün bunlar derleme zamanında gerçekleşir. Çalışma zamanı geldiğinde gitti. Evet, bir kod satırı ile karmaşık ADT'ler oluşturmanıza izin verir. Ama biliyor musun? Bir ADT sadece düz sıradan C unionarasında structs. Başka bir şey yok.

Gerçek katil tembel bir değerlendirmedir. Kodunuzun kesinliğini / tembelliğini doğru bulduğunuzda, hala zarif ve güzel olan aptalca hızlı kod yazabilirsiniz. Ancak bu şeyleri yanlış anlarsanız, programınız binlerce kez daha yavaş gider ve bunun neden olduğu açık değildir.

Örneğin, her baytın bir dosyada kaç kez göründüğünü saymak için önemsiz küçük bir program yazdım. 25KB giriş dosyası için, programın çalışması 20 dakika sürdü ve yutuldu 6 gigabayt RAM'i ! Bu çok saçma !! Ama sonra sorunun ne olduğunu anladım, tek bir patlama deseni ekledim ve çalışma süresi 0,02 saniyeye düştü .

İşte Haskell beklenmedik bir şekilde yavaş ilerliyor. Ve buna alışmak biraz zaman alır. Ancak zamanla, gerçekten hızlı kod yazmak daha kolay hale gelir.

Haskell'i bu kadar hızlı yapan nedir? Saflık. Statik tipler. Tembellik. Ancak her şeyden önce, derleyicinin kodunuzun beklentilerini bozmadan uygulamayı kökten değiştirebileceği kadar yüksek düzeyde olmak.

Ama sanırım bu sadece benim fikrim ...


13
@cimmanon Bence sadece fikir tabanlı. Diğer insanların muhtemelen cevap vermek istedikleri ilginç bir soru. Ama sanırım diğer seçmenlerin ne düşündüğünü göreceğiz.
Matematiksel

8
@cimmanon - bu arama yalnızca bir buçuk iş parçacığı verir ve hepsinin inceleme denetimleriyle ilgisi vardır. ve konuya verilen cevap "lütfen anlamadığınız şeyleri denetlemeyi bırakın" der. Birisi bunun cevabının mutlaka çok geniş olduğunu düşünürse, o zaman şaşıracaklar ve cevabın tadını çıkaracaklarını, çünkü cevap çok geniş olmadığından emin olabilirim.
sclv

34
"Diyelim ki Java'da JVM, boş işaretçiler olup olmadığını kontrol etmeli ve eğer birini ertelerseniz istisna atmalıdır." Java'nın örtük null denetimi (çoğunlukla) maliyetsizdir. Java uygulamaları, boş adresi eksik bir sayfayla eşleştirmek için sanal bellekten yararlanabilir ve yararlanabilir; bu nedenle, boş bir işaretçinin kaydı silindiğinde, Java düzeyinde yakalanan ve üst düzey bir istisna olarak atanan bir sayfa hatası tetiklenir. Bu nedenle, çoğu boş denetim CPU'daki bellek eşleme birimi tarafından ücretsiz olarak yapılır.
Ocak'ta Boann

4
@cimmanon: Belki de bunun nedeni, Haskell kullanıcılarının aslında açık fikirli insanlardan oluşan samimi bir grup olan tek topluluk gibi görünmesidir… Naziler kurallı bir köpek yiyen-köpek topluluğu yerine her fırsatta birbirlerine yeni bir tane yırtın… ki bu “normal” olduğunu düşündüğünüz gibi görünüyor.
Evi1M4chine

14
@MathematicalOrchid: Orijinal programınızın çalışması için 20 dakika süren bir kopyanız var mı? Bence neden bu kadar yavaş olduğunu incelemek oldukça öğretici olur.
George

79

Uzun zamandır işlevsel dillerin hızlı, özellikle de tembel fonksiyonel dillerin hızlı olamayacağı düşünülüyordu. Ancak bunun nedeni, ilk uygulamalarının özünde yorumlanmış olması ve gerçekten derlenmemiş olmasıdır.

İkinci bir tasarım dalgası, grafik azaltmaya dayalı olarak ortaya çıktı ve çok daha verimli bir derleme olasılığını açtı. Simon Peyton Jones bu araştırmayı , Fonksiyonel Programlama Dillerinin Uygulanması ve fonksiyonel dillerin uygulanması adlı iki kitabında yazdı : (Wadler ve Hancock'un bölümleri olan birincisi ve David Lester ile yazılmış). (Lennart Augustsson ayrıca eski kitap için önemli bir motivasyonun, kapsamlı bir şekilde yorumlanmayan LML derleyicisinin derlemesini nasıl gerçekleştirdiğini anlattığını da bildirdi.

Bu çalışmalarda anlatıldığı gibi grafik azaltma yaklaşımlarının ardındaki temel düşünce, bir programı bir talimatlar dizisi olarak değil, bir dizi yerel azaltma ile değerlendirilen bir bağımlılık grafiği olarak düşünmemizdir . İkinci temel görüş, böyle bir grafiğin değerlendirilmesinin yorumlanması gerekmediği, bunun yerine grafiğin kendisinin koddan oluşturulabileceğidir . Özellikle, bir grafiğin bir düğümünü "bir değer ya da" opcode "ve üzerinde çalışacak değerler" olarak değil, çağrıldığında istenen değeri döndüren bir işlev olarak gösterebiliriz. İlk çağrıldığında, alt düğümlerden değerlerini sorar ve daha sonra bunları çalıştırır ve daha sonra "sonucu döndür " yazan yeni bir talimatla kendi üzerine yazar.

Bu, GHC'nin bugün hala nasıl çalıştığına dair temelleri ortaya koyan daha sonraki bir makalede açıklanmaktadır (modulo birçok çeşitli ince ayar olsa da): "Stok Donanımına Tembel Fonksiyonel Dillerin Uygulanması: Spineless Tagless G-Machine." . GHC için mevcut yürütme modeli GHC Wiki'de daha ayrıntılı olarak belgelenmiştir .

Yani içgörü, makinelerin nasıl çalıştığının "temel" olduğunu düşündüğümüz "veri" ve "kod" un kesin ayrımının nasıl olması gerektiği değil, derleyicilerimiz tarafından dayatıldığıdır. Böylece bunu atabiliriz ve kendi kendini değiştiren kod (yürütülebilir) üreten bir kod (derleyici) alabiliriz ve hepsi oldukça iyi çalışabilir.

Böylece, makine mimarileri belirli bir anlamda zorunlu olsa da, diller geleneksel C tarzı akış kontrolü gibi görünmeyen çok şaşırtıcı şekillerde haritalanabilir ve yeterince düşük seviyeli olduğunu düşünürsek, bu da verimli.

Bunun da ötesinde, özellikle saflık ile açılan daha birçok "optimizasyon" vardır, çünkü daha geniş bir aralıkta "güvenli" dönüşümlere izin verir. Bu dönüşümleri, işleri daha iyi hale getirecek ve kötüleşmeyecek şekilde ne zaman ve nasıl uygulayacağımız elbette ampirik bir sorudur ve bu ve diğer birçok küçük seçenek üzerinde, yıllar süren çalışma hem teorik çalışmaya hem de pratik kıyaslamaya dahil edilmiştir. Bu tabii ki de bir rol oynuyor. Bu tür araştırmalara iyi bir örnek sağlayan bir makale " Hızlı Köri Yapmak: Yüksek Dereceli Diller için Bas / Gir ve Değerlendir / Uygula " dır .

Son olarak, bu modelin dolaylı indirimler nedeniyle hala ek yük getirdiğini belirtmek gerekir. İşleri katı bir şekilde yapmanın "güvenli" olduğunu bildiğimiz ve bu nedenle grafik indirimlerini elide ettiğimiz durumlarda bu önlenebilir. Sıkılığı / talebi çıkaran mekanizmalar GHC Wiki'de tekrar ayrıntılı olarak belgelenmiştir .


2
Bu talep analizörü bağlantısı altın ağırlığına değer! Son olarak, konu hakkında temelde açıklanamaz kara büyü gibi davranmayan bir şey. Bunu hiç duymadım mı ?? Herkesin tembellikle nasıl başa çıkacağını sorduğu her yerden bağlanmalıdır!
Evi1M4chine

@ Evi1M4chine Bir talep analizörü ile ilgili bir bağlantı göremiyorum, belki bir şekilde kayboldu. Birisi bağlantıyı geri yükleyebilir veya referansı netleştirebilir mi? Kulağa ilginç geliyor.
Cris P

1
@CrisP Son bağlantıya atıfta bulunduğuna inanıyorum. GHC Wiki'de GHC'deki talep analizörü hakkında bir sayfaya gider.
Serp C

@Serpentine Cougar, Chris P: Evet, demek istediğim buydu.
Evi1M4chine

19

Burada yorum yapacak çok şey var. Elimden geldiğince cevap vermeye çalışacağım.

Doğru kullanıldığında, düşük seviyeli dillere yakın olabilir.

Deneyimlerime göre, çoğu durumda Rust'un 2 katına çıkmak genellikle mümkündür. Ancak, düşük seviyeli dillere kıyasla performansın kötü olduğu bazı (geniş) kullanım durumları da vardır.

hatta yenebilir, ancak GHC Haskell'i C'yi derlediğinden, verimsiz bir C programı kullandığınız anlamına gelir)

Bu tamamen doğru değil. Haskell, daha sonra yerel kod üreteci aracılığıyla birleştirilmek üzere derlenen C-- (bir C alt kümesi) derler. Yerel kod üreticisi genellikle C derleyicisinden daha hızlı kod üretir, çünkü sıradan bir C derleyicisinin yapamayacağı bazı optimizasyonları uygulayabilir.

Makine mimarileri kabaca turing makinelerine dayanarak açıkça zorunludur.

Bunu düşünmenin iyi bir yolu değil, özellikle modern işlemciler talimatları düzenli olarak ve muhtemelen aynı zamanda değerlendirecek.

Gerçekten de, Haskell'in belirli bir değerlendirme sırası bile yok.

Aslında Haskell , dolaylı olarak bir değerlendirme sırası tanımlar.

Ayrıca, makine veri türleriyle uğraşmak yerine, her zaman cebirsel veri türlerini yaparsınız.

Yeterince gelişmiş bir derleyiciniz varsa, birçok durumda karşılık gelirler.

Fonksiyonları anında oluşturmanın ve onları atmanın bir programı yavaşlatacağını düşünürdünüz.

Haskell derlenir ve böylece daha üst düzey işlevler aslında anında yaratılmaz.

Haskell kodunu optimize ediyor gibi görünüyor, daha fazla makine yerine daha zarif ve soyut hale getirmeniz gerekiyor.

Genel olarak, kodu daha "makine gibi" yapmak Haskell'de daha iyi performans elde etmenin verimsiz bir yoludur. Ama daha soyut yapmak da her zaman iyi bir fikir değildir. Ne olduğunu iyi bir fikir ortak veri yapıları ve (bağlı listeler gibi) ağır optimize edilmiş fonksiyonlar kullanıyor.

f x = [x]ve f = pureHaskell'de de aynı şey. İyi bir derleyici önceki durumda daha iyi performans vermez.

Haskell (GHC ile derlenmiş), soyut doğası ve fiziksel makinelerden farklılıkları göz önüne alındığında neden bu kadar hızlı?

Kısa cevap "tam olarak bunu yapmak için tasarlandığı için" dir. GHC, spineless etiketsiz g-makinesini (STG) kullanır. Bu konuda bir makale okuyabilirsiniz burada (Bu oldukça karışık). GHC, katılık analizi ve iyimser değerlendirme gibi birçok başka şey de yapar .

C ve diğer zorunlu dillerin Turing Makineleri'ne biraz benzemesinin sebebi (ancak Haskell'in Lambda Calculus'a benzediği ölçüde değil), zorunlu bir dilde, sınırlı sayıda duruma (akarak hattı numarası) sahip olmanızdır. bir Bantla (koç), böylece durum ve mevcut bant banda ne yapılacağını belirler.

O zaman bu karışıklık değişebilirliğin daha yavaş koda yol açması mıdır? Haskell'in tembellik aslında değişebilirliğin düşündüğünüz kadar önemli olmadığı anlamına gelir, ayrıca yüksek seviyedir, böylece derleyicinin uygulayabileceği birçok optimizasyon vardır. Dolayısıyla, bir kaydı yerinde değiştirmek nadiren C gibi bir dilde olduğundan daha yavaş olacaktır.


3

Haskell (GHC) neden bu kadar hızlı?

Haskell'in performansını en son ölçtüğümden beri bir şey önemli ölçüde değişmiş olmalı . Örneğin:

  • Yazarın Haskell'in geliştirmenin Go (130s) ve OCaml (67s) 'den daha yavaş (1.020s) daha uzun sürdüğünü ve çalıştığını, yani OCaml'den 15x daha yavaş olduğunu tespit ettiği bir RRD dosya işleme karşılaştırması.
  • Bir basit sözlük kriter Haskell ~ 10x daha yavaş F # daha çalışıyor gösterdi.
  • Ray Tracer Dil Karşılaştırma Haskell C daha yavaş başka bir örnektir ++, OCaml ve hatta Java ve Lisp.
  • Bir paralel genel quicksort Haskell% 55 daha yavaş F # fazla oldu ve önemli ölçüde daha fazla kod gerektirir.

Peki ne değişti? Ne bir soru ne de mevcut cevaplarından herhangi birinin doğrulanabilir herhangi bir kıyaslama ölçütüne ve hatta koduna atıfta bulunmadığını fark ettim.

Haskellers'ın en sevdiği şey, C'nin% 5'ine girmeyi denemek ve almaktır

Herkesin yakınlarda herhangi bir yere ulaştığı doğrulanabilir sonuçlara ilişkin referanslarınız var mı?


6
Birisi tekrar üç kez aynanın önünde Harrop'un adını söyledi mi?
Chuck Adams

2
Hala, bu bütün giriş değil 10x, ancak edilir yutturmaca ve işkembe pazarlama. GHC gerçekten de hız açısından C'ye yaklaşabiliyor hatta bazen üstesinden gelebiliyor, ancak bu genellikle C'nin programlamasından çok farklı olmayan oldukça dahil, düşük seviyeli bir programlama tarzı gerektiriyor. ne yazık ki. kod ne kadar yüksek olursa, genellikle o kadar yavaş olur. alan sızıntıları, uygun ancak düşük performanslı ADT türleri ( cebirsel , söz verdiği gibi soyut değil ), vb.
Will Ness

1
Bunu sadece gönderiyorum çünkü bugün gördüm chrispenner.ca/posts/wc . Haskell'de yazılmış ve c sürümünü yenen wc yardımcı programının bir uygulamasıdır.
Garnizon

3
@Garrison bağlantı için teşekkürler . 80 satır, "düşük seviyeli programlama tarzı C'nin kendisinde programlamaktan çok farklı değil" dediğim şeydir. . "yüksek seviye kodu", bu "aptal" olurdu fmap (length &&& length . words &&& length . lines) readFile. Eğer o daha hızlı oldu (hatta karşılaştırılabilir kadar) C burada yutturmaca tamamen haklı olurdu o zaman . Haskell'de hızda C için olduğu gibi hala çok çalışmamız gerekiyor.
Ness Ness

2
Reddit reddit.com/r/programming/comments/dj4if3/… üzerine bu tartışmaya göre, Haskell kodunun gerçekten buggy olduğuna (örneğin, boşlukla başlayan veya biten satırlardaki boşluklar, à'da kırmalar) ve diğerleri iddia edilen performans sonuçlarını üretemez.
Jon Harrop
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.