通过核心进行性能分析
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
但是,如果我使用 w
和 f
的注释替代项,代码将在 ~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
的定义。只需看看 modInteger
和 plusInteger
在两个转储中出现的位置。
看起来在赋值 w = f 1 0 g
中它内联了 f
的定义,因此它不必在每次调用 go
时计算 w
。
更具体地说,f 1 0 g
不依赖于 go
的任何参数 - 即。 s
, i
or g
, 所以可以去掉它的计算。
即使在表达式 f 1 0 g
中将 g
传递给 f
,它实际上并没有被使用。
以下代码在我的电脑上运行大约 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μ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
的定义。只需看看 modInteger
和 plusInteger
在两个转储中出现的位置。
看起来在赋值 w = f 1 0 g
中它内联了 f
的定义,因此它不必在每次调用 go
时计算 w
。
更具体地说,f 1 0 g
不依赖于 go
的任何参数 - 即。 s
, i
or g
, 所以可以去掉它的计算。
即使在表达式 f 1 0 g
中将 g
传递给 f
,它实际上并没有被使用。