在 Haskell 中循环 Unboxed 数组的性能

标签 performance haskell monads

首先,它很棒。但是,我遇到了一种情况,我的基准测试结果很奇怪。我是 Haskell 的新手,这是我第一次接触可变数组和 Monad。以下代码基于 this example .

我写了一个通用的 monadic for 函数,它接受数字和一个阶跃函数而不是一个范围(就像 forM_ 那样)。我将使用我的通用 for 函数(循环 A)与嵌入等效的递归函数(循环 B)进行了比较。拥有循环 A 明显快于拥有循环 B。奇怪的是,同时拥有循环 A 和 B 比单独拥有循环 B 更快(但比单独拥有循环 A 稍慢)。

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

  • 关于 Haskell 如何从 monadic 函数中提取结果,我还没有学到一些东西。
  • 循环 B 以比循环 A 低缓存效率的方式使阵列出错。为什么?
  • 我犯了一个愚蠢的错误; Loop A和Loop B实际上是不同的。
    • 请注意,在循环 A 和循环 B 中的一个或两个循环的所有 3 种情况下,程序都会产生相同的输出。

这是代码。我用 ghc -O2 for.hs 使用 GHC 版本 6.10.4 测试了它。

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

最佳答案

我刚刚尝试用 Criterion 对其进行基准测试和 GHC 6.12.1,循环 A 对我来说看起来只稍微快一点。我绝对不会得到奇怪的“两者一起比单独 B 快”的效果。

此外,如果您的 step 函数真的只是一个步骤并且没有对其参数做任何古怪的事情,则以下版本的 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

这是 Criterion 的结果,其中 loopA' 是使用我的 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<

代码如下:

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 ]

关于在 Haskell 中循环 Unboxed 数组的性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2815002/

相关文章:

vim - (vim 分析)VIM 256 色模式,滞后的 php 文件

windows - 如何测量 Windows 上的内存带宽利用率?

java - 当找到多个集合的交集时,使用retainAll()的最快顺序

haskell - 如何将高阶函数应用于 Haskell 中的有效函数?

haskell - Monads如何被认为是纯的?

c++ - 传递引用的开销是多少?

haskell - 所有给定的项目都具有属性

haskell,链接过滤器

haskell - 函数单子(monad)真的提供了比函数应用仿函数更多的东西吗?如果是这样,是什么?

haskell - 扩大 ocaml 中的类型