通过核心进行性能分析

Performance Analysis via Core

以下代码在我的电脑上运行大约 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

但是,如果我使用 wf 的注释替代项,代码将在 ~31μs 内运行!这让我很惊讶,因为我改变得很少,而且 f 在 3,840 次迭代中每次运行两次(即代码几乎没有被使用)。

我去核心调查了。这是 slow version and fast version.

-ddump-simpl 的相关部分

不幸的是,我无法从核心看出是什么造成了如此巨大的差异。我看到的主要区别是在快速版本中,GHC 已经意识到 f 不需要 gen 参数。但肯定不会产生 45x/2 个数量级的性能差异。

源代码有点做作(不需要或不使用几个参数),所以我的主要问题是关于核心的:我没有看到任何表明如此巨大的性能差异的差异。分析核心时我错过了什么?作为后续,我可以在 first/slow 版本的源代码级别做些什么来让它像 second/fast 版本一样执行?

看起来在快速版本中 GHC 解除了计算:

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

超出了 go 的定义。只需看看 modIntegerplusInteger 在两个转储中出现的位置。

看起来在赋值 w = f 1 0 g 中它内联了 f 的定义,因此它不必在每次调用 go 时计算 w。 更具体地说,f 1 0 g 不依赖于 go 的任何参数 - 即。 s, i or g, 所以可以去掉它的计算。

即使在表达式 f 1 0 g 中将 g 传递给 f,它实际上并没有被使用。