Следующий код работает примерно 1,5 мс на моем компьютере (скомпилирован с GHC 8.0.1 и -02):Анализ производительности с помощью Ядра
import Criterion
import Data.Bits
import Data.Int
import Criterion.Main
main :: IO()
main = defaultMain [bench "mybench" $ nf (mybench 3840) (0 :: Int)]
mybench :: Int -> Int -> Double
{-# INLINE mybench #-}
mybench n = go 0 n
where go s i g | i == 0 = s
| otherwise =
let (w,_) = f 1 0 g
--w = f 1 0 g
f mag v gen | mag >= 18446744073709551616000 = (v,gen)
--f mag v gen | mag >= 18446744073709551616000 = v
| otherwise = v' `seq` f (mag*18446744073709551616 :: Integer) v' gen where
x = -8499970308474009078 :: Int
v' = (v * 18446744073709551616 + (fromIntegral x + 9223372036854775808))
y = fromInteger ((-9223372036854775808) + w `mod` 18446744073709551616)
coef = (fromIntegral (9007199254740991 .&. (y::Int64)) :: Double)/9007199254740992
z = 2.0 * (-0.5 + coef)
in go (z+s) (i-1) g
Однако, если я использую закомментированные заместителям w
и f
, код работает в ~ 31 мкс! Это было удивительно для меня, так как я изменился очень мало, и потому, что f
запускается дважды для каждой из 3840 итераций (т. Е. Код едва используется).
Я пошел в ядро, чтобы исследовать. Вот соответствующие части -ddump-simpl
от slow version и fast version.
К сожалению, я не могу видеть из ядра то, что делает такую огромную разницу. Основное отличие, которое я вижу в том, что в быстрой версии GHC понял, что f
не нужен аргумент gen
. Но, безусловно, это не может сделать разницу в производительности на 45 или 2 порядка.
Исходный код немного изобретателен (несколько аргументов не нужны или не используются), поэтому мой основной вопрос касается ядра: я не вижу различий, которые бы указывали на столь резкую разницу в производительности. Что мне не хватает при анализе ядра? В качестве продолжения, что я мог сделать на исходном уровне первой/медленной версии, чтобы заставить ее работать как вторая/быстрая версия?
Я вижу, что это произошло, но почему это имеет какие-либо последствия для производительности? Должна ли не «встроенная» версия (от медленного ядра) быть медленнее? – crockeea
GHC переместил вычисление 'w' за пределы вызова' go'. Несмотря на то, что вы набросали 'mybench', он все еще пересматривает' w' каждый раз, когда вызывается 'go'. Это не происходит в быстрой версии. – ErikR
Итак, когда GHC приводит к вычету из расчета, я получаю * sharing *. В этом есть смысл! – crockeea