当前位置:  开发笔记 > 编程语言 > 正文

使用ST monad

如何解决《使用STmonad》经验,为你挑选了1个好方法。

在博客文章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和两个项目的简单模板.)



1> rampion..:

你的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
  ]

推荐阅读
mylvfamily
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有