4

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

mapM_ print [1..100000000]

C比使用write(2)非缓冲系统调用的简单 for 循环慢约 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));
}
4

3 回答 3

10

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

第一个重要的区别是

mapM_ print [1..100000000]

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

添加类型签名,以便 Haskell 代码在Ints 上工作,

mapM_ print [1 :: Int .. 100000000]

将时间缩短到 47 秒,略高于 C 代码所用时间的两倍。

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

然后使用 s 的链表Char填充字节缓冲区,然后将其写入stdout句柄。

因此,Haskell 代码比 C 代码做了更多、更复杂的事情,因此花费更长的时间也就不足为奇了。

诚然,希望有一种简单的方法可以更直接(因此更快)输出这些东西。但是,处理它的正确方法是使用更合适的算法(也适用于 C)。一个简单的改变

putStr . unlines $ map show [0 :: Int .. 100000000]

所花费的时间几乎减半,如果想要真正快速,可以使用更快的ByteStringI/O 并有效地构建输出,如applicative 的答案所示。

于 2012-11-11T19:04:03.200 回答
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 快几倍。或者更确切地说,通常缓冲的 Haskell 比具有 write(2) 非缓冲系统调用的 C 快。

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

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

于 2012-11-11T22:46:34.467 回答
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也就是说,与丹尼尔菲舍尔令人惊讶的失败主义相比,做得比 好九倍是孩子的游戏 。您需要知道的一切都在这里:http ://hackage.haskell.org/packages/archive/bytestring/0.10.2.0/doc/html/Data-ByteString-Builder.html我不会将它与您的 C 进行比较,因为我的结果比 Daniel 和 nm 慢得多,所以我认为出了点问题。

编辑:使导入与所有版本一致。bytestring-0.10.x在我看来,以下内容可能更清楚——Builder相当于unlines . map show

main = hPutBuilder stdout $ unlines_ $ map intDec [0..100000000::Int]
 where unlines_ = mconcat . map (<> charUtf8 '\n')
于 2012-11-12T08:37:39.627 回答