2016-08-29 3 views
3

Следующий код работает примерно 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 порядка.

Исходный код немного изобретателен (несколько аргументов не нужны или не используются), поэтому мой основной вопрос касается ядра: я не вижу различий, которые бы указывали на столь резкую разницу в производительности. Что мне не хватает при анализе ядра? В качестве продолжения, что я мог сделать на исходном уровне первой/медленной версии, чтобы заставить ее работать как вторая/быстрая версия?

ответ

4

Похоже, в быстрой версии GHC перебросить вычисления:

y = fromInteger ((-9223372036854775808) + w `mod` 18446744073709551616) 

из определения go. Посмотрите, где находятся modInteger и plusInteger.

Похоже, в назначении w = f 1 0 g это встраиваемое определение f так, что он не должен вычислить w при каждом вызове go. В частности, f 1 0 g не зависит от каких-либо параметров от go - т.е. s, i или g, и поэтому его вычисление можно снять.

Несмотря на то, что g передано f в выражении f 1 0 g, оно фактически не используется.

+0

Я вижу, что это произошло, но почему это имеет какие-либо последствия для производительности? Должна ли не «встроенная» версия (от медленного ядра) быть медленнее? – crockeea

+0

GHC переместил вычисление 'w' за пределы вызова' go'. Несмотря на то, что вы набросали 'mybench', он все еще пересматривает' w' каждый раз, когда вызывается 'go'. Это не происходит в быстрой версии. – ErikR

+0

Итак, когда GHC приводит к вычету из расчета, я получаю * sharing *. В этом есть смысл! – crockeea