另一个微基准测试:为什么这个“循环”(用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));
}
答案 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')