Haskell中的基本I / O性能

时间:2012-11-11 18:25:44

标签: haskell ghc

另一个微基准测试:为什么这个“循环”(用ghc -O2 -fllvm编译,7.4.1,Linux 64bit 3.2内核,重定向到/dev/null

mapM_ print [1..100000000]

比使用C非缓冲系统调用的普通write(2)中的简单for-cycle慢约5倍?我正在努力收集Haskell陷阱。

即使这种缓慢的C解决方案也比Haskell

快得多
int i;
char buf[16];
for (i=0; i<=100000000; i++) {
    sprintf(buf, "%d\n", i);
    write(1, buf, strlen(buf));
}

3 个答案:

答案 0 :(得分:10)

好的,在我的盒子上,每gcc -O3编译的C代码大约需要21.5秒才能运行,原始的Haskell代码大约需要56秒。所以不是因子5,略高于2.5。

第一个非常重要的区别是

mapM_ print [1..100000000]

使用Integer s,这有点慢,因为它涉及预先检查,然后使用加框的Int,而Show Int实例执行转换工作在未装箱的Int# s。

添加类型签名,以便Haskell代码在Int s上运行,

mapM_ print [1 :: Int .. 100000000]

将时间缩短到47秒,比C代码花费的时间高出两倍。

现在,另一个很大的区别是show生成了一个Char的链表,并且不只是填充一个连续的字节缓冲区。那也慢了。

然后,Char的链接列表用于填充一个字节缓冲区,然后写入stdout句柄。

因此,Haskell代码比C代码做了更多,更复杂的事情,因此花费更长时间并不奇怪。

不可否认,有一种简单的方法可以更直接地输出这些东西(因此更快)。但是,处理它的正确方法是使用更合适的算法(也适用于C)。

的简单改动
putStr . unlines $ map show [0 :: Int .. 100000000]

几乎将所花费的时间减半,如果想要它真的很快,就会使用更快的ByteString I / O并有效地构建输出,如applicative's answer中所示。

答案 1 :(得分:7)

在我(相当慢和过时)的机器上,结果是:

$ time haskell-test > haskell-out.txt
real    1m57.497s
user    1m47.759s
sys     0m9.369s
$ time c-test > c-out.txt
real    7m28.792s
user    1m9.072s
sys     6m13.923s
$ diff haskell-out.txt c-out.txt
$

(我修复了列表,以便C和Haskell都以0开头)。

是的,你看对了。 Haskell比C快几倍。或者说,通常使用write(2)非缓冲系统调用缓冲Haskell比C更快。

(当测量输出到/ dev / null而不是真正的磁盘文件时,C大约快1.5倍,但是谁关心/ dev / null性能?)

技术数据:Intel E2140 CPU,2核,1.6 GHz,1M缓存,Gentoo Linux,gcc4.6.1,ghc7.6.1。

答案 2 :(得分:5)

将巨型字节串传递给操作系统的标准Haskell方法是使用构造器monoid。

import Data.ByteString.Lazy.Builder  -- requires bytestring-0.10.x
import Data.ByteString.Lazy.Builder.ASCII -- omit for bytestring-0.10.2.x
import Data.Monoid
import System.IO

main = hPutBuilder stdout $ build  [0..100000000::Int]

build = foldr add_line mempty
   where add_line n b = intDec n <> charUtf8 '\n' <> b

给了我:

 $ time ./printbuilder >> /dev/null
 real   0m7.032s
 user   0m6.603s
 sys    0m0.398s

与您使用的Haskell方法相比

$ time ./print >> /dev/null
real    1m0.143s
user    0m58.349s
sys 0m1.032s

也就是说,孩子的比赛比mapM_ print好9倍,这与丹尼尔费舍尔令人惊讶的失败主义相反。您需要知道的一切都在这里:http://hackage.haskell.org/packages/archive/bytestring/0.10.2.0/doc/html/Data-ByteString-Builder.html我不会将它与您的C进行比较,因为我的结果比Daniel和N.m慢得多。所以我认为出了问题。

编辑:使导入与bytestring-0.10.x的所有版本保持一致我发现以下内容可能更清晰 - Builder相当于unlines . map show

main = hPutBuilder stdout $ unlines_ $ map intDec [0..100000000::Int]
 where unlines_ = mconcat . map (<> charUtf8 '\n')