IOUArray和STUArray之间的性能差异(在Prime Sieve中)

时间:2017-03-22 15:34:37

标签: performance haskell

我使用可变数组在Haskell中制作了一个主筛。两个主要的可变数组是IOUArraySTUArray所以我用两者检查了性能。

在我的计算机上,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功能。

2 个答案:

答案 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 :: ...

允许在primeSieveIOprimeSieveST中生成专门的循环代码。