对于特定任务,我需要在可变数组中进行大量快速,单独的写入。为了检查性能,我使用了以下测试:
size :: Int
size = 256*256*16
arr :: UArray Int Int
arr = runST $ do
arr <- newArray (0,size) 0 :: ST s (STUArray s Int Int)
forM_ [0..size] $ \i -> do
writeArray arr i i
unsafeFreeze arr
arr_sum = foldl' (\ sum i -> sum + (arr ! i)) 0 [0..size-1]
main = print arr_sum
结果如下:
vh:haskell apple1$ ghc -O3 bench.hs -o bench; time ./bench
Linking bench ...
549755289600
real 0m0.748s
user 0m0.697s
sys 0m0.048s
我怀疑它不应该花0.7秒来填充内存上的256 * 256 * 16阵列,所以我在JavaScript中测试了一个等效的程序:
size = 256*256*16;
x = new Array(size);
s = 0;
for (var i=0; i<size; ++i)
x[i] = i;
for (var i=0; i<size; ++i)
s += x[i];
console.log(s);
结果是:
vh:haskell apple1$ time node bench.js
549755289600
real 0m0.175s
user 0m0.150s
sys 0m0.024s
在C上,时间是0.012s
,这是一个很好的下限。
#include <stdio.h>
#define SIZE (256*256*16)
double x[SIZE];
int main(){
int i;
double s = 0;
for (i = 0; i<SIZE; ++i)
x[i] = i;
for (i = 0; i<SIZE; ++i)
s += x[i];
printf("%f",s);
};
所以这几乎证实了我的假设,即我的Haskell程序正在做其他事情,而不仅仅是填充数组并在之后对它进行求和。在某处可能存在隐藏的堆栈,但由于我使用了foldl'
和forM_
,我无法识别它,我认为这些编译为无堆栈代码。那么,这里效率低下的根源是什么?
答案 0 :(得分:5)
GHC不会像你用C完成的那样产生很好的紧密循环。根据我的经验,在运行时间中,因子为3,大约相当于该课程。
要获得更好的性能,请使用Vector库:
import qualified Data.Vector.Unboxed as V
size = 256*256*16 :: Int
doit = V.foldl' (+) 0 vec
where vec = V.generate size id
main = print doit
答案 1 :(得分:2)
这对评论来说太大了,但不是真的答案。你追踪的进口有点烦人,我也压制了-Wall
的警告(重要的是要注意当你在看表演时):
module Main where
import Data.Array.Unboxed
import Data.Array.ST
import Data.Array.Unsafe
import Control.Monad.ST
import Control.Monad
import Data.List
size :: Int
size = 256*256*16
ar :: UArray Int Int
ar = runST $ do
a <- newArray (0,size) 0 :: ST s (STUArray s Int Int)
forM_ [0..size] $ \i -> do
writeArray a i i
unsafeFreeze a
arrSum :: Int
arrSum = foldl' (\ s i -> s + (ar ! i)) 0 [0..size-1]
main :: IO ()
main = print arrSum
用于haskell和节点repsectively:
jberryman /tmp » time ./t
-524288
./t 0.04s user 0.01s system 92% cpu 0.056 total
jberryman /tmp » time nodejs t.js
549755289600
nodejs t.js 0.19s user 0.01s system 100% cpu 0.200 total
对于GHC 7.8和7.6(我必须import Data.Array.ST hiding (unsafeFreeze)
,基本相同的时间,但其他代码是相同的。)
编辑:糟糕,看着我不是很善于观察;请注意,在我的32位机器上,计数溢出haskell,但不是JS,所以我们有另外的苹果到橙子;这里更公平的比较可能是Integer
。
我绝对建议进行任何微观基准测试的标准,否则你会让自己浪费很多时间。
另外,我不相信您在C
版本中初始化阵列的开销很大,所以它不是一个公平的比较。
答案 2 :(得分:2)
抱歉,我想我只能正确回答这个问题。对于任何好奇的人来说,原因与代码无关,但是当我对它们进行基准测试时,GHC没有使用-O2
重新编译我自动构建的二进制文件。解决方案是使用force-rrecomp
标志:
ghc -fforce-recomp -O2 bench.hs -o bench
#haskell @ freenode上的人建议的更好的解决方案是正确设置Cabal,并使用它进行构建。