在博客文章http://galvanist.com/post/83741037068/adding-badly-under-python-julia-go中,作者使用一种简单的算法来比较各种语言(包括Haskell)的性能。在Haskell示例中,作者使用递归函数。作为练习,我想使用ST monad来允许本地可变状态。这是有效的,但递归函数比我使用ST monad的函数快得多。
递归函数 -
peanoAdd :: Int -> Int -> Int
peanoAdd 0 y = y
peanoAdd x y = peanoAdd (x - 1) (y + 1)
main :: IO ()
main = do
let a = 64000000 :: Int
let b = 64000000 :: Int
let n = peanoAdd a b
print n
128000000
real 0m0.583s
user 0m0.480s
sys 0m0.096s
使用ST monad -
import Control.Monad.ST
import Data.STRef
import Control.Monad.Loops
peanoAdd :: Int -> Int -> Int
peanoAdd x y = runST $ do
x' <- newSTRef x
y' <- newSTRef y
whileM_ (do x'' <- readSTRef x'
return $ x'' /= 0)
(do modifySTRef x' (subtract 1)
modifySTRef y' (+1))
readSTRef y'
main :: IO ()
main = do
let a = 64000000 :: Int
let b = 64000000 :: Int
let n = peanoAdd a b
print n
128000000
real 0m17.837s
user 0m16.412s
sys 0m1.424s
我在做一些明显错误的事情是否会损害ST monad示例中的性能? (PS。我正在使用Stack和两个项目的简单模板。)
答案 0 :(得分:7)
您的ST程序运行缓慢的一个原因是您正在使用modifySTRef
, which is non-strict:
警告
modifySTRef
不严格应用此功能。这个 表示程序是多次调用modifySTRef
,但很少使用 价值,thunk会堆积在内存中导致空间泄漏。 这是使用STRef作为计数器时常见的错误。对于 例如,以下内容将泄漏内存并可能产生堆栈 溢出:print $ runST $ do ref <- newSTRef 0 replicateM_ 1000000 $ modifySTRef ref (+1) readSTRef ref
您的x'
每次循环被强制执行一次,但y'
不会被迫print
,因此会产生巨大的链接。
使用modifySTRef'
对我的笔记本电脑进行基准测试,显示严格程度如何提高运行时间(尽管两者仍然会失去递归版本)。
benchmarking rec
time 7.896 ms (7.602 ms .. 8.269 ms)
0.992 R² (0.988 R² .. 0.997 R²)
mean 7.842 ms (7.724 ms .. 8.001 ms)
std dev 404.5 μs (303.9 μs .. 523.8 μs)
variance introduced by outliers: 25% (moderately inflated)
benchmarking st
time 18.44 ms (17.84 ms .. 19.01 ms)
0.996 R² (0.993 R² .. 0.998 R²)
mean 18.03 ms (17.79 ms .. 18.41 ms)
std dev 750.4 μs (528.0 μs .. 1.110 ms)
variance introduced by outliers: 16% (moderately inflated)
benchmarking st'
time 9.191 ms (9.028 ms .. 9.437 ms)
0.996 R² (0.992 R² .. 0.999 R²)
mean 9.317 ms (9.175 ms .. 9.527 ms)
std dev 475.8 μs (311.8 μs .. 677.9 μs)
variance introduced by outliers: 25% (moderately inflated)
基准代码:
import Criterion.Main
import Control.Monad.ST
import Data.STRef
import Control.Monad.Loops
peanoAddST :: Int -> Int -> Int
peanoAddST x y = runST $ do
x' <- newSTRef x
y' <- newSTRef y
whileM_ (do x'' <- readSTRef x'
return $ x'' /= 0)
(do modifySTRef x' (subtract 1)
modifySTRef y' (+1))
readSTRef y'
peanoAddST' :: Int -> Int -> Int
peanoAddST' x y = runST $ do
x' <- newSTRef x
y' <- newSTRef y
whileM_ (do x'' <- readSTRef x'
return $ x'' /= 0)
(do modifySTRef' x' (subtract 1)
modifySTRef' y' (+1))
readSTRef y'
peanoAddRec :: Int -> Int -> Int
peanoAddRec 0 y = y
peanoAddRec x y = peanoAddRec (x - 1) (y + 1)
main =
let n = 64000 in
defaultMain
[ bench "rec" $ whnf (peanoAddRec n) n
, bench "st" $ whnf (peanoAddST n) n
, bench "st'" $ whnf (peanoAddST' n) n
]