我使用可变数组在Haskell中制作了一个主筛。两个主要的可变数组是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
我分析了代码(程序由堆栈管理,如果重要),这里是与.prof
文件的主要区别:
使用IOUArray
:
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
使用STUArray
:
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
因此,出于某种原因,写入功能对STUArray来说要花费更长的时间。该程序是在OSX上使用堆栈构建和运行的。第61行:32-53的功能是writeArray
功能。
答案 0 :(得分:1)
我不确定您的测量结果是什么偏差,但criterion
用以下内容替换char const*
main
无论我输入什么输入import Criterion
import Criterion.Main
main = do
n <- readLn
defaultMain
[ bench "io" $ nfIO (primeSieveIO n)
, bench "st" $ nf primeSieveST n
]
(n
做一些带有参数的时髦东西,这就是从STDIN读取criterion
的原因),n
版本约为25 % 快点。这是一个互动:
ST
当我考虑文件写入时,$ ./test
100000
benchmarking io
time 201.2 ms (140.3 ms .. 280.0 ms)
0.943 R² (0.797 R² .. 1.000 R²)
mean 214.3 ms (196.2 ms .. 237.6 ms)
std dev 26.22 ms (14.72 ms .. 32.72 ms)
variance introduced by outliers: 31% (moderately inflated)
benchmarking st
time 141.9 ms (139.8 ms .. 143.5 ms)
1.000 R² (0.999 R² .. 1.000 R²)
mean 141.0 ms (140.4 ms .. 141.6 ms)
std dev 815.1 μs (476.3 μs .. 1.139 ms)
variance introduced by outliers: 12% (moderately inflated)
版本更慢。你在编译这个或在GHCi中运行吗?如果是后者,请尝试编译。
答案 1 :(得分:1)
已编辑以添加更多详细信息和更好的解决方法。
我能够使用Stack lts-8.5复制问题,并使用-O3
进行编译。
在查看生成的GHC内核之后,看起来在IO情况下,编译器能够生成markOff
的专用版本,用于内联数组写入(字面上内联位中的位设置)数组)进入循环。对于ST情况,它使用markOff
的通用版本,并且每次写入都会通过在unsafeWrite
类型上调度的MArray
多态调用,这种调用要慢得多。
如果您添加markOff
名为markOffST
的副本,其中包含专门用于STUArray
的签名:
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
然后在primeSieveST
中使用它,编译器使用内联写入为markOffST
生成上述特化,并且您将发现ST版本与IO版本一样快(在至少在使用-O3
进行编译时。
很难说这是否是编译器&#34; bug&#34;或不。在没有一点帮助的情况下,GHC并没有在ST案例中产生markOff
的专用版本的飞跃。
不更改签名的解决方法是要求GHC内联markOff
函数:
{-# INLINE markOff #-}
markOff :: ...
允许在primeSieveIO
和primeSieveST
中生成专门的循环代码。