Я нахожусь в середине портирования оригинала Дэвида Блей C implementation выделения скрытого распределения Дирихле в Haskell, и я пытаюсь решить, оставить ли некоторые из низкоуровневых вещей в C. Следующая функция - один пример - это приближение второй производной lgamma
:Как повысить производительность этого численного вычисления в 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);
}
Я перевел это в более или менее идиоматической Haskell следующим образом:
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)
проблема заключается в том, что, когда я бегу как через Criterion, моя версия Haskell в шесть или семь раз медленнее r (я компилирую с -O2
на GHC 6.12.1). Некоторые подобные функции еще хуже.
Я практически ничего не знаю о производительности Haskell, и меня не очень интересует digging through Core или что-то в этом роде, так как я всегда могу просто назвать кучу математических функций C через FFI.
Но мне любопытно, есть ли плохие фрукты, которые мне не хватает, - какое-то расширение или библиотека или аннотация, которые я мог бы использовать, чтобы ускорить эту цифровую штуку, не делая ее слишком уродливой.
UPDATE: Вот две лучшие решения, благодаря Don Stewart и Yitz. Я немного изменил ответ Ицца, чтобы использовать Data.Vector
.
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
Производительность двух, кажется, почти точно так же, с той или другой выигрыш на один процентный пункт или два в зависимости от флагов компилятора.
Как camccann сказал over at Reddit, мораль истории - «Для достижения наилучших результатов используйте Дон Стюарт в качестве генератора кода GHC». Запрет на это решение, самая безопасная ставка, по-видимому, заключается в простом преобразовании структур управления C непосредственно в Haskell, хотя слияние фьюжн может придать аналогичную производительность в более идиоматическом стиле.
Я, вероятно, в конечном итоге воспользуюсь подходом Data.Vector
в своем коде.
Программа C использует петлю, в то время как в Haskell вы используете в кучу списки. У них не будет такой же производительности. Лучшее, что нужно сделать, это прямое преобразование структуры управления и данных в Haskell, чтобы поддерживать ту же производительность. –
Привет Трэвис! Вы отпустите свой код, когда закончите? Я обнаружил, что могу понять ваш Haskell на основе кода C. Возможно, мне удастся научиться Haskell таким образом. –
Вы должны проверить код FastInvSqrt. – Puppy