Nasıl Haskell bu sayısal hesaplama performansını artırmak için?
Ben oldukça meşgulüm taşıma David Blei orijinal C implementation Gizli Öyle Tahsisi için Haskell, ve yapmaya karar verdim olsun bırakın bazı düşük seviyeli şeyler C. aşağıdaki işlevi bir örnek—bir yaklaşım, ikinci türevin lgamma
:
double trigamma(double x)
{
double p;
int i;
x=x 6;
p=1/(x*x);
p=(((((0.075757575757576*p-0.033333333333333)*p 0.0238095238095238)
*p-0.033333333333333)*p 0.166666666666667)*p 1)/x 0.5*p;
for (i=0; i<6 ;i )
{
x=x-1;
p=1/(x*x) p;
}
return(p);
}
Daha fazla veya daha az deyimsel aşağıdaki gibidir: Haskell içine bu tercüme ettim
trigamma :: Double -> Double
trigamma x = snd $ last $ take 7 $ iterate next (x' - 1, p')
where
x' = x 6
p = 1 / x' ^ 2
p' = p / 2 c / x'
c = foldr1 (\a b -> (a b * p)) [1, 1/6, -1/30, 1/42, -1/30, 5/66]
next (x, p) = (x - 1, 1 / x ^ 2 p)
Sorun çalıştırdığımda her iki Criterion Haskell benim sürüm üzerinden altı veya yedi kat daha yavaş (DZD 6.12.1 -O2
hazırlıyorum). Bazı benzer işlevleri daha da kötü.
Haskell performansı hakkında neredeyse hiçbir şey bilmiyorum, ve her zaman sadece FFI ile matematik yoğun bir avuç C fonksiyonları arayabilirim beri çok digging through Core ya da böyle bir şey ile ilgilenen, ben değilim.
Ama ben merak edip orada kolay lokma olduğumu kayıp bir tür uzantısı ya da Kütüphane ya da açıklama bu olabilir hızlandırın bu sayısal şeyler olmadan yapmak çok çirkin.
GÜNCELLEME:Burada iki daha iyi çözümler, Don Stewart sayesinde Yitz. Biraz Yitz cevap Data.Vector
kullanmak için modifiye ettim.
invSq x = 1 / (x * x)
computeP x = (((((5/66*p-1/30)*p 1/42)*p-1/30)*p 1/6)*p 1)/x 0.5*p
where p = invSq x
trigamma_d :: Double -> Double
trigamma_d x = go 0 (x 5) $ computeP $ x 6
where
go :: Int -> Double -> Double -> Double
go !i !x !p
| i >= 6 = p
| otherwise = go (i 1) (x-1) (1 / (x*x) p)
trigamma_y :: Double -> Double
trigamma_y x = V.foldl' ( ) (computeP $ x 6) $ V.map invSq $ V.enumFromN x 6
İkisinin performansı aynı neredeyse, bir veya diğer puan veya iki derleyici bayrakları bağlı olarak kazanmış gibi görünüyor.
camccann over at Reddit bu hikayenin ana fikri "en iyi sonuç İçin, DZD arka uç kod jeneratör." olarak Don Stewart kullanmak olduğunu söyledi Bu çözüm engelleme, güvenli bahis loop fusion daha deyimsel bir tarzda benzer performans verebilir ancak C kontrol yapılarını doğrudan Haskell çevirmek için sadece görünüyor.
Muhtemelen benim kod Data.Vector
yaklaşım kullanarak sonuna kadar veririm.
CEVAP
Aynı kontrol ve veri yapıları kullanmak, verimli:
{-# LANGUAGE BangPatterns #-}
{-# OPTIONS_GHC -fvia-C -optc-O3 -fexcess-precision -optc-march=native #-}
{-# INLINE trigamma #-}
trigamma :: Double -> Double
trigamma x = go 0 (x' - 1) p'
where
x' = x 6
p = 1 / (x' * x')
p' =(((((0.075757575757576*p-0.033333333333333)*p 0.0238095238095238)
*p-0.033333333333333)*p 0.166666666666667)*p 1)/x' 0.5*p
go :: Int -> Double -> Double -> Double
go !i !x !p
| i >= 6 = p
| otherwise = go (i 1) (x-1) (1 / (x*x) p)
Senin testsuite yok, ama bu aşağıdaki kanamayla verir:
A_zdwgo_info:
cmpq $5, %r14
jg .L3
movsd .LC0(%rip), %xmm7
movapd %xmm5, %xmm8
movapd %xmm7, %xmm9
mulsd %xmm5, %xmm8
leaq 1(%r14), %r14
divsd %xmm8, %xmm9
subsd %xmm7, %xmm5
addsd %xmm9, %xmm6
jmp A_zdwgo_info
Tamam görünüyor. Bu -fllvm
arka uç iyi bir iş yok kod türüdür.
GCC ya da Şablon Haskell veya manuel çözümü ile de döngü unrolls ve yapmanın tek yolu. Eğer bu bir sürü yapıyor eğer o (TH makro) düşünebilirsiniz.
Aslında, DZD LLVM arka uç döngü göz önüne sermek yapar :-)
Eğer gerçekten orijinal Haskell versiyonu gibi son olarak, eğer, stream fusion combinators, kullanarak yazma ve DZD geri döngü haline dönüştürür. (Okuyucu için bir egzersiz).
Nasıl ASP.NET MVC Uygulama Performansı...
Nasıl ayarı yanlış LinearLayout perfor...
Nasıl en iyi performansını artırmak iç...
Nasıl Eclipse performansını artırmak i...
Nasıl alfa-sayısal rasgele bir dize ol...