Ich bin in der Mitte von David Blei Original C implementation Latent Dirichlet Allocation zu Haskell portieren, und ich versuche zu entscheiden, ob einige der Low-Level-Zeug in C verlassen. Die folgende Funktion ist ein Beispiel-es ist ein Annäherung der zweiten Ableitung von lgamma
:Wie verbessert man die Leistung dieser numerischen Berechnung in Haskell?
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);
}
ich dies in mehr oder weniger idiomatische Haskell übersetzt haben, wie folgt:
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)
Das Problem ist, dass, wenn ich laufen beide durch Criterion, meine Haskell-Version ist sechs oder sieben mal langsamer r (Ich kompiliere mit -O2
auf GHC 6.12.1). Einige ähnliche Funktionen sind noch schlimmer.
Ich weiß praktisch nichts über Haskell Leistung, und ich bin nicht sehr interessiert an digging through Core oder etwas ähnliches, da ich immer nur die Handvoll mathematisch intensive C-Funktionen über FFI aufrufen kann.
Aber ich bin neugierig, ob es tief hängende Früchte gibt, die ich vermisse - eine Art Erweiterung oder Bibliothek oder Annotation, mit denen ich diese Zahlen beschleunigen könnte, ohne sie zu hässlich zu machen.
UPDATE: Hier sind zwei bessere Lösungen, dank Don Stewart und Yitz. Ich habe Yitz 'Antwort leicht modifiziert, um Data.Vector
zu verwenden.
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
Die Leistung der beiden scheint fast genau die gleiche zu sein, mit dem einen oder anderen zu gewinnen um einen Prozentpunkt oder zwei auf den Compiler-Flags abhängig.
Wie camccann sagte over at Reddit, die Moral der Geschichte ist "Für beste Ergebnisse, verwenden Sie Don Stewart als Ihre GHC-Backend-Code-Generator." Abgesehen von dieser Lösung scheint die sicherste Wette nur darin zu bestehen, die C-Kontrollstrukturen direkt in Haskell zu übersetzen, obwohl die Schleifenfusion eine ähnliche Leistung in einem idiomatischen Stil liefern kann.
Ich werde wahrscheinlich am Ende mit der Data.Vector
Ansatz in meinem Code.
Das C-Programm verwendet Loops, während in Haskell Sie verwenden Heap-Listen. Sie werden nicht die gleiche Leistung haben. Am besten ist es, die Kontroll- und Datenstrukturen direkt in Haskell zu übersetzen, um die gleiche Leistung zu erhalten. –
Hallo Travis! Werden Sie Ihren Code freigeben, wenn Sie fertig sind? Ich fand heraus, dass ich Ihren Haskell anhand des C-Codes verstehen konnte. Vielleicht wäre es mir möglich, Haskell auf diese Weise zu lernen. –
Sie sollten den FastInvSqrt-Code überprüfen. – Puppy