Tembellik
Bir "derleyici optimizasyonu" değildir, ancak dil spesifikasyonu tarafından garanti edilen bir şeydir, böylece her zaman gerçekleşmesine güvenebilirsiniz. Esasen, bu, sonuçla "bir şeyler yapana" kadar iş yapılmadığı anlamına gelir. (Tembelliği kasten kapatmak için birkaç şeyden birini yapmadığınız sürece.)
Bu, açıkçası, kendi başına bütün bir konudur ve SO'nun zaten bu konuda birçok soru ve cevabı vardır.
Naçizane deneyimlerime göre, kodunuz çok tembel ya da çok sıkı hale sahiptir ölçüde (zaman içinde daha büyük performans cezalar ve ben hakkında konuşmak üzere olduğum diğer şeyler hepsinden daha uzayda) ...
Sıkılık analizi
Tembellik gerekli olmadıkça işten kaçınmakla ilgilidir. Derleyici belirli bir sonucun "her zaman" gerekli olacağını belirleyebilirse, hesaplamayı saklayıp daha sonra gerçekleştirmeye zahmet etmez; sadece doğrudan gerçekleştirir, çünkü bu daha verimlidir. Buna "katılık analizi" denir.
Açıkçası, açıkçası, derleyici bir şeyin ne zaman katılabileceğini her zaman tespit edemez . Bazen derleyiciye küçük ipuçları vermeniz gerekir. (Sıkılık analizinin, Çekirdek çıktısı üzerinden geçmekten başka, ne düşündüğünü yapıp yapmadığını belirlemenin kolay bir yolunun farkında değilim.)
satır içine almak
Bir işlevi çağırırsanız ve derleyici hangi işlevi çağırdığınızı söyleyebilirse, bu işlevi "satır içi" yapmayı deneyebilir - yani işlev çağrısını işlevin kendisinin bir kopyasıyla değiştirmek. Bir işlev çağrısının yükü genellikle oldukça küçüktür, ancak satır içi çizgi genellikle başka türlü gerçekleşmeyecek olan diğer optimizasyonların gerçekleşmesini sağlar, bu nedenle satır içi işlem büyük bir kazanç olabilir.
İşlevler yalnızca "yeterince küçük" ise (veya özellikle satır içi sormayı gerektiren bir pragma eklerseniz) satır içine alınır. Ayrıca, işlevler yalnızca derleyici hangi işlevi çağırdığınızı söyleyebiliyorsa satır içine alınabilir. Derleyicinin söyleyememesinin iki ana yolu vardır:
Aradığınız işlev başka bir yerden iletilirse. Örneğin, filter
işlev derlendiğinde, kullanıcı tarafından sağlanan bir argüman olduğu için filtre yüklemini satır içi yapamazsınız.
Aradığınız işlev bir sınıf yöntemiyse ve derleyici hangi türün dahil olduğunu bilmiyorsa. Örneğin, sum
işlev derlendiğinde, derleyici +
işlevi satır içine alamaz , çünkü sum
her biri farklı bir +
işleve sahip olan birkaç farklı sayı türüyle çalışır.
İkinci durumda, {-# SPECIALIZE #-}
belirli bir türe sabit kodlanmış bir işlevin sürümlerini oluşturmak için pragmayı kullanabilirsiniz . Örneğin, tür için sabit kodlanmış {-# SPECIALIZE sum :: [Int] -> Int #-}
bir sürümü derler , yani bu sürümde satır içine alınabilir.sum
Int
+
Bununla birlikte, yeni özel sum
fonksiyonumuzun yalnızca derleyici ile birlikte çalıştığımızı söyleyebileceğini unutmayın Int
. Aksi takdirde orijinal, polimorfik sum
çağrılır. Yine, gerçek fonksiyon çağrısı yükü oldukça küçüktür. Satırlamanın sağlayabileceği ek optimizasyonlar faydalıdır.
Ortak alt ifade eleme
Belirli bir kod bloğu aynı değeri iki kez hesaplarsa, derleyici bunu aynı hesaplamanın tek bir örneğiyle değiştirebilir. Örneğin,
(sum xs + 1) / (sum xs + 2)
derleyici bunu optimize edebilir
let s = sum xs in (s+1)/(s+2)
Derleyicinin her zaman bunu yapmasını bekleyebilirsiniz . Ancak, görünüşe göre bazı durumlarda bu daha kötü performansa neden olabilir, daha iyi değil, bu yüzden GHC her zaman bunu yapmaz . Açıkçası, bunun arkasındaki detayları gerçekten anlamıyorum. Ancak sonuçta, eğer bu dönüşüm sizin için önemliyse, bunu manuel olarak yapmak zor değildir. (Ve eğer önemli değilse, neden endişeleniyorsun?)
Vaka ifadeleri
Aşağıdakileri göz önünde bulundur:
foo (0:_ ) = "zero"
foo (1:_ ) = "one"
foo (_:xs) = foo xs
foo ( []) = "end"
İlk üç denklem listenin boş olup olmadığını kontrol eder (diğer şeylerin yanı sıra). Ama aynı şeyi üç kez kontrol etmek israftır. Neyse ki, derleyicinin bunu birkaç iç içe kasa ifadesine optimize etmesi çok kolaydır. Bu durumda,
foo xs =
case xs of
y:ys ->
case y of
0 -> "zero"
1 -> "one"
_ -> foo ys
[] -> "end"
Bu daha az sezgisel, ancak daha verimlidir. Derleyici bu dönüşümü kolayca yapabildiğinden, endişelenmenize gerek yoktur. Sadece desen eşleşmenizi mümkün olan en sezgisel bir şekilde yazın; derleyici bunu mümkün olduğunca hızlı yapmak için yeniden sıralamak ve yeniden düzenlemekte çok iyidir.
Füzyon
Liste işleme için standart Haskell deyimi, bir liste alan ve yeni bir liste üreten işlevleri birbirine zincirlemektir. Kanonik örnek
map g . map f
Ne yazık ki, tembellik gereksiz işi atlamayı garanti ederken, ara liste öz performansı için tüm tahsisler ve anlaşmaların yapılması. "Füzyon" veya "ormansızlaşma" derleyicinin bu ara adımları ortadan kaldırmaya çalıştığı yerdir.
Sorun, bu işlevlerin çoğunun özyinelemeli olmasıdır. Özyineleme olmadan, tüm işlevleri tek bir büyük kod bloğunda ezmek, sadeleştiriciyi çalıştırmak ve ara liste olmadan gerçekten en uygun kodu üretmek için satır içi bir alıştırma olacaktır. Ancak özyineleme nedeniyle bu işe yaramaz.
{-# RULE #-}
Bunlardan bazılarını düzeltmek için pragmalar kullanabilirsiniz . Örneğin,
{-# RULES "map/map" forall f g xs. map f (map g xs) = map (f.g) xs #-}
Şimdi GHC her map
başvuruda bulunduğunda map
, listeyi tek bir geçişte ezerek ara listeyi ortadan kaldırıyor.
Sorun şu ki, bu sadece map
takip için işe yarıyor map
. - Diğer birçok olasılık vardır map
ve ardından filter
, filter
ardından map
, vb daha çok "akış füzyonu" olarak adlandırılan, her biri için bir çözelti, icat edilmiştir elle kodu daha. Bu, burada tarif etmeyeceğim daha karmaşık bir numara.
Uzun ve kısa: Bunların hepsi programcı tarafından yazılan özel optimizasyon hileleridir . GHC'nin kendisi füzyon hakkında hiçbir şey bilmiyor; hepsi liste kütüphanelerinde ve diğer kap kütüphanelerinde. Bu nedenle, hangi optimizasyonların gerçekleştiği, kapsayıcı kitaplıklarınızın nasıl yazıldığına (veya daha gerçekçi olarak, hangi kitaplıkları kullanmayı seçtiğinize) bağlıdır.
Örneğin, Haskell '98 dizileriyle çalışıyorsanız, herhangi bir füzyon beklemeyin. Ancak vector
kütüphanenin geniş füzyon özelliklerine sahip olduğunu anlıyorum . Her şey kütüphanelerle ilgili; derleyici sadece RULES
pragmayı sağlar. (Bu arada, son derece güçlü. Bir kütüphane yazarı olarak, bunu istemci kodunu yeniden yazmak için kullanabilirsiniz!)
Meta:
"Önce kod, ikinci profil, üçüncü optimize" diyen insanlara katılıyorum.
"Verilen bir tasarım kararının maliyeti için zihinsel bir modele sahip olmak yararlı" diyen insanlarla da aynı fikirdeyim.
Her şeyde ve her şeyde denge ...