在博客文章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和两个项目的简单模板.)
你的ST程序运行缓慢的一个原因是你正在使用modifySTRef
,这是非严格的:
警告
modifySTRef
严格不要使用该功能.这意味着如果程序modifySTRef
多次调用,但很少使用该值,则thunk会堆积在内存中,从而导致空间泄漏.这是使用STRef作为计数器时常见的错误.例如,以下内容将泄漏内存并可能产生堆栈溢出:print $ runST $ do ref <- newSTRef 0 replicateM_ 1000000 $ modifySTRef ref (+1) readSTRef ref
您的x'
每循环一次被迫的,但y'
不强制,直到print
,所以有一个巨大的建立起来的thunk的链条.
在我的笔记本电脑上对使用的版本进行基准测试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 ]