Performance IOUArray和STUArray阵列之间的性能差异(在主筛中)
我使用可变数组在Haskell中制作了一个素筛。两个主要的可变数组是Performance IOUArray和STUArray阵列之间的性能差异(在主筛中),performance,haskell,Performance,Haskell,我使用可变数组在Haskell中制作了一个素筛。两个主要的可变数组是IOUArray和STUArray,因此我检查了这两个数组的性能 在我的计算机上,IOUArray的运行速度是STUArray的五倍,尽管代码结构基本相同。这是预期的吗?我是否缺少一些东西可以让STUArray运行得更快 编辑:我做了一些分析,结果可以在代码下面找到 代码如下: {-# LANGUAGE FlexibleContexts #-} module Main where import Data.Array.MArra
IOUArray
和STUArray
,因此我检查了这两个数组的性能
在我的计算机上,IOUArray
的运行速度是STUArray
的五倍,尽管代码结构基本相同。这是预期的吗?我是否缺少一些东西可以让STUArray
运行得更快
编辑:我做了一些分析,结果可以在代码下面找到
代码如下:
{-# LANGUAGE FlexibleContexts #-}
module Main where
import Data.Array.MArray
import Data.Array.IO
import Data.Array.ST
import Control.Monad.ST
import Data.Array.Unboxed
import Control.Monad
import System.IO
import System.Environment (getArgs)
main :: IO ()
main = mainIO
--main = mainST
mainST :: IO ()
mainST = do
[n',outdir] <- getArgs
let n = read n' :: Int
primes = primeSieveST n
writeFile outdir (unlines . map show $ primes)
putStrLn "Primes found using STUArray"
mainIO :: IO ()
mainIO = do
[n',outdir] <- getArgs
let n = read n' :: Int
primes <- primeSieveIO n
writeFile outdir (unlines . map show $ primes)
putStrLn "Primes found using IOUArray"
-- Prime sieve using IOUArray
primeSieveIO :: Int -> IO [Int]
primeSieveIO n = do
arr <- newArray (1,n) True :: IO (IOUArray Int Bool)
writeArray arr 1 False
let p=2
forM_ [p..n] $ \a -> do
v <- readArray arr a
if v then markOff arr a n
else return ()
iarr <- freeze arr :: IO (UArray Int Bool)
return . map fst . filter (\(_,a)-> a) $ assocs iarr
-- Prime sieve using STUArray
primeSieveST :: Int -> [Int]
primeSieveST n = map fst . filter (\(_,a) -> a) . assocs $ runSTUArray $ do
arr <- newArray (1,n) True
writeArray arr 1 False
let p = 2
forM_ [p..n] $ \a -> do
v <- readArray arr a
if v then markOff arr a n
else return ()
return arr
markOff :: (Integral i,Ix i, MArray a Bool m) => a i Bool -> i -> i -> m ()
markOff arr a n = do
forM_ [2*a,2*a+a..n] $ \b -> writeArray arr b False
使用STUArray
:
COST CENTRE MODULE SRC %time %alloc
markOff Main app/Main.hs:(60,1)-(61,53) 64.1 55.5
primeSieveIO Main app/Main.hs:(35,1)-(44,54) 25.2 34.7
mainIO Main app/Main.hs:(26,1)-(31,40) 5.9 9.8
markOff.\ Main app/Main.hs:61:32-53 3.6 0.0
primeSieveIO.\ Main app/Main.hs:(39,24)-(42,33) 1.3 0.0
COST CENTRE MODULE SRC %time %alloc
markOff.\ Main app/Main.hs:61:32-53 63.1 51.9
markOff Main app/Main.hs:(60,1)-(61,53) 26.7 33.7
primeSieveST Main app/Main.hs:(48,1)-(56,12) 7.5 10.9
mainST Main app/Main.hs:(18,1)-(23,40) 1.8 2.9
因此,出于某种原因,数组的写入函数花费的时间要长得多。该程序是使用OSX上的堆栈构建和运行的。第61:32-53行上的函数是
writeArray
函数。我不确定是什么影响了您的测量值,但将main
替换为以下内容
import Criterion
import Criterion.Main
main = do
n <- readLn
defaultMain
[ bench "io" $ nfIO (primeSieveIO n)
, bench "st" $ nf primeSieveST n
]
当我考虑文件写入时,
IO
版本要慢得多。您是在编译此文件还是在GHCi中运行?如果是后者,请尝试编译。已编辑,以添加更多细节和更好的解决方法
我可以用堆栈lts-8.5复制这个问题,用-O3
编译
在查看生成的GHC内核之后,看起来在IO情况下,编译器能够生成一个专门版本的markOff
,该版本将数组写入(字面上是将位数组中的位设置内联)内联到循环中。对于ST的情况,它使用了一个通用版本的markOff
,并且每次写入都要通过unsafeWrite
多态调用,该调用在确切的MArray
类型上调度,速度要慢得多
如果您添加名为markOffST
的markOffST
副本,且签名专用于STUArray
s:
markOffST :: (Integral i,Ix i) => STUArray s i Bool -> i -> i -> ST s ()
markOffST arr a n = do
forM_ [2*a,2*a+a..n] $ \b -> writeArray arr b False
然后在primesivest
中使用它,编译器通过内联写入为markOffST
生成上述专门化,您会发现ST版本与IO版本一样快(至少在使用-O3
编译时是如此)
很难说这是不是编译器的“bug”。GHC只是在没有一点帮助的情况下,没有在ST案例中生成专门版本的markOff
在不更改签名的情况下,一种解决方法是要求GHC内联markOff
函数:
{-# INLINE markOff #-}
markOff :: ...
它允许在
primesiveio
和primesiveest
中生成专门的循环代码,就我所知,ST monad最终是不可变的计算。在这种情况下,这是很自然的。ST
版本肯定会更快,因为runSTUArray
在内部使用,而您的IO
版本(不必要)使用复制版本。顺便说一句,GHC中正在进行的一些工作可能会导致ST
操作总体上得到更好的优化,而一些IO
操作会稍微慢一点(以获得更好的行为)。所以请坚持使用ST
。嗯。。。nf
和nfIO
是否评估整个[Int]
列表?这不只是比较第一个元素需要多少时间吗?(如果是这样的话,这真的有关系吗?@chinf
代表正常形式(与wnhf
相反),所以我希望这是针对整个列表的。@Alec,我正在用堆栈编译和执行。我只是做了一些分析,结果被添加到我原来的帖子中。稍后我将尝试标准
基准测试。是的,这似乎有效。这是ghc中的错误吗?当类型签名太一般时,它无法正确优化。我在回答中添加了更多细节。我不确定这本身就是一个bug。优化版的markOff
只能针对比一般签名更特殊的类型签名生成;只是对于IO的情况,GHC会继续生成一个额外的、专门的定义,它不会为ST版本生成这个定义。
{-# INLINE markOff #-}
markOff :: ...