通过Core进行性能分析
以下代码在我的计算机上运行约1.5ms(使用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μs! 这对我来说是令人惊讶的,因为我改变的很少,并且因为f
每运行3,840次迭代(即代码几乎不用)会运行两次。
我去核心调查。 以下是缓慢版本和快速版本中-ddump-simpl
的相关部分。
不幸的是,我无法从核心看到造成如此巨大差异的原因。 我看到的主要区别在于,在快速版本中,GHC已经意识到f
不需要gen
论证。 但肯定不能使45x / 2数量级的性能差异。
源代码有点人为设计(几个参数不需要或使用),所以我的主要问题是关于核心:我没有看到任何可能表明如此巨大性能差异的差异。 分析核心时我错过了什么? 作为后续,我可以在第一个/慢版本的源代码级别做些什么来使它像第二个/快速版本一样执行?
它看起来像在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 1 0 g
传递给f
,它实际上并没有被使用。