Performance Haskell中非固定数组上的循环性能

Performance Haskell中非固定数组上的循环性能,performance,haskell,monads,Performance,Haskell,Monads,首先,它很棒。然而,我遇到了一种情况,我的基准测试结果很奇怪。我是Haskell的新手,这是我第一次接触可变数组和单子。下面的代码基于 我为函数编写了一个通用的一元,它接受数字和一个步进函数,而不是一个范围(就像forM那样)。我比较了使用泛型for函数(循环A)和嵌入等效递归函数(循环B)。使用循环A明显比使用循环B快。更奇怪的是,将循环A和B放在一起比单独使用循环B快(但比单独使用循环A慢一点) 对于这些差异,我能想到一些可能的解释。请注意,这些只是猜测: 关于Haskell如何从一元函数

首先,它很棒。然而,我遇到了一种情况,我的基准测试结果很奇怪。我是Haskell的新手,这是我第一次接触可变数组和单子。下面的代码基于

我为函数编写了一个通用的一元
,它接受数字和一个步进函数,而不是一个范围(就像
forM
那样)。我比较了使用泛型
for
函数(循环A)和嵌入等效递归函数(循环B)。使用循环A明显比使用循环B快。更奇怪的是,将循环A和B放在一起比单独使用循环B快(但比单独使用循环A慢一点)

对于这些差异,我能想到一些可能的解释。请注意,这些只是猜测:

  • 关于Haskell如何从一元函数中提取结果,我还没有学到一些东西
  • 循环B以低于循环a的缓存效率的方式对阵列进行故障诊断。为什么
  • 我犯了一个愚蠢的错误;循环A和循环B实际上是不同的。
    • 请注意,在所有3种情况下,如果循环A和循环B中有一个或两个,程序将产生相同的输出
这是代码。我使用ghc版本6.10.4用.hs的ghc-O2对它进行了测试

import Control.Monad
import Control.Monad.ST
import Data.Array.IArray
import Data.Array.MArray
import Data.Array.ST
import Data.Array.Unboxed

for :: (Num a, Ord a, Monad m) => a -> a -> (a -> a) -> (a -> m b) -> m ()
for start end step f = loop start where
    loop i
        | i <= end   = do
            f i
            loop (step i)
        | otherwise  = return ()

primesToNA :: Int -> UArray Int Bool
primesToNA n = runSTUArray $ do
    a <- newArray (2,n) True :: ST s (STUArray s Int Bool)
    let sr = floor . (sqrt::Double->Double) . fromIntegral $ n+1

    -- Loop A
    for 4 n (+ 2) $ \j -> writeArray a j False

    -- Loop B
    let f i
        | i <= n     = do
            writeArray a i False
            f (i+2)
        | otherwise  = return ()
        in f 4

    forM_ [3,5..sr] $ \i -> do
        si <- readArray a i
        when si $
            forM_ [i*i,i*i+i+i..n] $ \j -> writeArray a j False
    return a

primesTo :: Int -> [Int]
primesTo n = [i | (i,p) <- assocs . primesToNA $ n, p]

main = print $ primesTo 30000000
import-Control.Monad
进口管制站
导入Data.Array.IArray
导入Data.Array.MArray
导入Data.Array.ST
导入Data.Array.unbox
对于::(数字a,单词a,单子m)=>a->a->a->a->a->(a->m->b)-->m()
对于开始-结束步骤f=循环开始,其中
回路一
|我是雷·因特布尔
primesToNA n=runSTUArray$do
双人房)。fromIntegral$n+1
--环路A
对于4 n(+2)$\j->writeArray a j False
--回路B
让我
|我知道
我写的是a j错误
归还
primesTo::Int->[Int]

primesTo n=[i |(i,p)也许可以与枪战nsieve程序进行比较和对比?在任何情况下,了解真正发生了什么的唯一方法是(例如使用)

{-#选项-O2-optc-O-fbang模式-fglasgow exts-optc-march=pentium4}
--
--计算机语言枪战
-- http://shootout.alioth.debian.org/
--
--唐·斯图尔特2005贡献
--ST monad布尔数组上的nsieve
--
进口管制站
导入Data.Array.ST
导入Data.Array.Base
导入系统
进口管制
导入数据
导入文本.Printf
main=do
n>=readIO.head::IO Int
地图(\i->sieve(10000`shiftL`(n-i))[0,1,2]
筛n=do

让r=runST(做a我刚刚试着用和GHC 6.12.1来做基准测试,循环a对我来说看起来只是稍微快一点。我绝对不会得到奇怪的“两者加在一起比B单独快”的效果

此外,如果您的步长函数实际上只是一个步长,并且其参数没有做任何奇怪的事情,那么下面版本的
for
似乎要快一点,尤其是对于较小的数组:

for' :: (Enum a, Num a, Ord a, Monad m) => a -> a -> (a -> a) -> (a -> m b) -> m ()
for' start end step = forM_ $ enumFromThenTo start (step start) end
以下是标准的结果,其中
loopA'
是您使用my
for'
的循环A,其中
loopC
同时是A和B:

benchmarking loopA...
mean: 2.372893 s, lb 2.370982 s, ub 2.374914 s, ci 0.950
std dev: 10.06753 ms, lb 8.820194 ms, ub 11.66965 ms, ci 0.950

benchmarking loopA'...
mean: 2.368167 s, lb 2.354312 s, ub 2.381413 s, ci 0.950
std dev: 69.50334 ms, lb 65.94236 ms, ub 73.17173 ms, ci 0.950

benchmarking loopB...
mean: 2.423160 s, lb 2.419131 s, ub 2.427260 s, ci 0.950
std dev: 20.78412 ms, lb 18.06613 ms, ub 24.99021 ms, ci 0.950

benchmarking loopC...
mean: 4.308503 s, lb 4.304875 s, ub 4.312110 s, ci 0.950
std dev: 18.48732 ms, lb 16.19325 ms, ub 21.32299 ms, ci 0.950<
基准测试环。。。
平均值:2.372893秒,磅2.370982秒,ub 2.374914秒,置信区间0.950
标准偏差:10.06753毫秒,磅8.820194毫秒,ub 11.66965毫秒,置信区间0.950
对loopA’进行基准测试。。。
平均值:2.368167秒,磅2.354312秒,ub 2.381413秒,置信区间0.950
标准偏差:69.50334毫秒,磅65.94236毫秒,ub 73.17173毫秒,置信区间0.950
基准循环B。。。
平均值:2.423160秒,磅2.419131秒,ub 2.427260秒,置信区间0.950
标准偏差:20.78412毫秒,磅18.06613毫秒,ub 24.99021毫秒,置信区间0.950
基准测试循环。。。
平均值:4.308503秒,磅4.304875秒,ub 4.312110秒,置信区间0.950
标准偏差:18.48732毫秒,磅16.19325毫秒,ub 21.32299毫秒,ci 0.950<
下面是代码:

module Main where

import Control.Monad
import Control.Monad.ST
import Data.Array.ST
import Data.Array.Unboxed

import Criterion.Main

for :: (Num a, Ord a, Monad m) => a -> a -> (a -> a) -> (a -> m b) -> m ()
for start end step f = loop start where
    loop i
        | i <= end   = do
            f i
            loop (step i)
        | otherwise  = return ()

for' :: (Enum a, Num a, Ord a, Monad m) => a -> a -> (a -> a) -> (a -> m b) -> m ()
for' start end step = forM_ $ enumFromThenTo start (step start) end

loopA  arr n = for  4 n (+ 2) $ flip (writeArray arr) False
loopA' arr n = for' 4 n (+ 2) $ flip (writeArray arr) False

loopB arr n =
  let f i | i <= n     = do writeArray arr i False
                            f (i+2)
          | otherwise  = return ()
  in f 4

loopC arr n = do
  loopA arr n
  loopB arr n

runPrimes loop n = do
    let sr = floor . (sqrt::Double->Double) . fromIntegral $ n+1
    a <- newArray (2,n) True :: (ST s (STUArray s Int Bool))

    loop a n

    forM_ [3,5..sr] $ \i -> do
        si <- readArray a i
        when si $
            forM_ [i*i,i*i+i+i..n] $ \j -> writeArray a j False
    return a

primesA  n = [i | (i,p) <- assocs $ runSTUArray $ runPrimes loopA  n, p]
primesA' n = [i | (i,p) <- assocs $ runSTUArray $ runPrimes loopA' n, p]
primesB  n = [i | (i,p) <- assocs $ runSTUArray $ runPrimes loopB  n, p]
primesC  n = [i | (i,p) <- assocs $ runSTUArray $ runPrimes loopC  n, p]

main = let n = 10000000 in
  defaultMain [ bench "loopA"  $ nf primesA  n
              , bench "loopA'" $ nf primesA' n
              , bench "loopB"  $ nf primesB  n
              , bench "loopC"  $ nf primesC  n ]
modulemain其中
进口管制
进口管制站
导入Data.Array.ST
导入Data.Array.unbox
进口标准.Main
对于::(数字a,单词a,单子m)=>a->a->a->a->a->(a->m->b)-->m()
对于开始-结束步骤f=循环开始,其中
回路一

|我选择手动实现循环,而不是使用forM_uu,因为在我尝试时它会快一点。我想
for
for'
取决于很多事情。@yairchu:对不起,我打错了,并且已经更正了。如果我突然发现我能够从未来开始使用编译器,我会特别小心我很担心。
module Main where

import Control.Monad
import Control.Monad.ST
import Data.Array.ST
import Data.Array.Unboxed

import Criterion.Main

for :: (Num a, Ord a, Monad m) => a -> a -> (a -> a) -> (a -> m b) -> m ()
for start end step f = loop start where
    loop i
        | i <= end   = do
            f i
            loop (step i)
        | otherwise  = return ()

for' :: (Enum a, Num a, Ord a, Monad m) => a -> a -> (a -> a) -> (a -> m b) -> m ()
for' start end step = forM_ $ enumFromThenTo start (step start) end

loopA  arr n = for  4 n (+ 2) $ flip (writeArray arr) False
loopA' arr n = for' 4 n (+ 2) $ flip (writeArray arr) False

loopB arr n =
  let f i | i <= n     = do writeArray arr i False
                            f (i+2)
          | otherwise  = return ()
  in f 4

loopC arr n = do
  loopA arr n
  loopB arr n

runPrimes loop n = do
    let sr = floor . (sqrt::Double->Double) . fromIntegral $ n+1
    a <- newArray (2,n) True :: (ST s (STUArray s Int Bool))

    loop a n

    forM_ [3,5..sr] $ \i -> do
        si <- readArray a i
        when si $
            forM_ [i*i,i*i+i+i..n] $ \j -> writeArray a j False
    return a

primesA  n = [i | (i,p) <- assocs $ runSTUArray $ runPrimes loopA  n, p]
primesA' n = [i | (i,p) <- assocs $ runSTUArray $ runPrimes loopA' n, p]
primesB  n = [i | (i,p) <- assocs $ runSTUArray $ runPrimes loopB  n, p]
primesC  n = [i | (i,p) <- assocs $ runSTUArray $ runPrimes loopC  n, p]

main = let n = 10000000 in
  defaultMain [ bench "loopA"  $ nf primesA  n
              , bench "loopA'" $ nf primesA' n
              , bench "loopB"  $ nf primesB  n
              , bench "loopC"  $ nf primesC  n ]