Performance 为什么这个简单的Haskell程序这么慢?
在这个打印从1到10000000的所有数字(Haskell版本和C版本)的简单程序中,为什么Haskell版本速度如此之慢,哪些命令有助于学习如何提高Haskell程序的性能 下面是一份报告,包含重现我激动人心的事件所需的所有细节,制作报告时会打印来源,包括Makefile的来源:Performance 为什么这个简单的Haskell程序这么慢?,performance,haskell,Performance,Haskell,在这个打印从1到10000000的所有数字(Haskell版本和C版本)的简单程序中,为什么Haskell版本速度如此之慢,哪些命令有助于学习如何提高Haskell程序的性能 下面是一份报告,包含重现我激动人心的事件所需的所有细节,制作报告时会打印来源,包括Makefile的来源: $ make -B report cat Foo.hs import Data.Foldable main = traverse_ print [1..10000000] cat Fooc.c #include &l
$ make -B report
cat Foo.hs
import Data.Foldable
main = traverse_ print [1..10000000]
cat Fooc.c
#include <stdio.h>
int main()
{
for (int n = 0; n < 10000000; ++n)
{
printf("%d\n", n+1);
}
}
ghc -O3 Foo.hs -o Foo
time ./Foo | tail -n1
3.45user 0.03system 0:03.49elapsed 99%CPU (0avgtext+0avgdata 4092maxresident)k
0inputs+0outputs (0major+290minor)pagefaults 0swaps
10000000
cc -O3 Fooc.c -o Fooc
time ./Fooc | tail -n1
0.63user 0.02system 0:00.66elapsed 99%CPU (0avgtext+0avgdata 1468maxresident)k
0inputs+0outputs (0major+63minor)pagefaults 0swaps
10000000
cat Makefile
.PHONY: printFoo printFooc printMakefile
printFoo: Foo.hs
cat $^
printFooc: Fooc.c
cat $^
printMakefile: Makefile
cat $^
Fooc: CFLAGS=-O3
Fooc: Fooc.c
Foo: Foo.hs
ghc -O3 $^ -o $@
.PHONY: timeFoo timeFooc
timeFoo: Foo
time ./$^ | tail -n1
timeFooc: Fooc
time ./$^ | tail -n1
.PHONY: report
report: printFoo printFooc timeFoo timeFooc printMakefile
$make-B报告
猫福酒店
导入数据。可折叠
main=遍历打印[1..10000000]
猫食
#包括
int main()
{
对于(int n=0;n<10000000;++n)
{
printf(“%d\n”,n+1);
}
}
ghc-O3 Foo.hs-o Foo
时间./Foo | tail-n1
3.45用户0.03系统0:03.49运行99%CPU(0avgtext+0avgdata 4092maxresident)k
0输入+0输出(0主要+290次要)页面故障0交换
10000000
cc-O3 Fooc.c-o Fooc
时间./Fooc | tail-n1
0.63用户0.02系统0:00.66运行99%CPU(0avgtext+0avgdata 1468maxresident)k
0输入+0输出(0大或+63小)页面故障0交换
10000000
cat生成文件
.PHONY:printFoo printFooc printMakefile
printFoo:Foo.hs
猫$^
printFooc:Fooc.c
猫$^
printMakefile:Makefile
猫$^
Fooc:CFLAGS=-O3
Fooc:Fooc.c
Foo:Foo.hs
ghc-O3$^-o$@
.冒牌货:timeFoo timeFooc
timeFoo:Foo
时间./$^尾部-n1
timeFooc:Fooc
时间./$^尾部-n1
.伪造:报告
报告:printFoo printFooc timeFoo timeFooc printMakefile
在我的系统上,Haskell代码大约需要3.2秒
注意,你的C代码需要
time ./fooc | tail -n1
ld: warning: directory not found for option '-L/opt/local/lib'
10000000
./fooc 0.92s user 0.03s system 33% cpu 2.863 total
tail -n1 2.85s user 0.01s system 99% cpu 2.865 total
所以一定要注意时间a | b
的区别,以及它与时间(a | b)
的区别
哈斯克尔之所以迟钝,部分原因是(其中一些是假设)
print
和底层的putStrLn
使用String
,这是一个字符链接列表-- Using functions from the Relude package
main = traverse_ putBSLn (show <$> [(1::Int)..10000000])
因此,CPU时间更接近C程序,这让我假设这种差异主要是由于Haskell的前奏曲中默认使用的例程中内置了不必要的UTF8处理
死胡同:
- 我尝试了
和largeNoBuffering
,但没有成功BlockBuffering
- 构造一个大的bytestring并用一个调用进行打印并没有任何好处(懒惰或严格的bytestring)
- 通过
而不是文本
打印只得到了最轻微的改进字符串
- 直接呈现到ByteString,而不是将值
ed打包为字符串。如果做得好,我想这可能是一场胜利show
{-# LANGUAGE OverloadedStrings #-}
import Data.ByteString.Builder
import System.IO (stdout)
import Data.Foldable
main :: IO ()
main = do
traverse_ (hPutBuilder stdout . (<>"\n") . intDec) [(1::Int)..10000000]
实际上,这比之前对hPut的许多单独调用更有效,因为正如hPutBuilder所说:
此函数比hPut更有效。toLazyByteString,因为在许多情况下不需要进行缓冲区分配。此外,短构建器的多个执行的结果连接在句柄缓冲区中,因此避免了不必要的缓冲区刷新
因此,我应该补充:4。在这种情况下,Haskell的速度很慢,因为有时计算不会融合,最终会得到多余的分配,这是不免费的。请使用
Int64
,或Int
overInteger
。还要注意,两种计时都包括两种不同的内容:生成值的计算和打印值的I/O。如果你把它们分开,你的结果会更有信息。例如,print(last[1::Int..10000000])
)将只打印列表中的最后一个元素(而使用Int
而不是Integer
,正如其他人所建议的那样)。人们,如果你要发表评论,请确保你的观点是正确的Integer
vs常量大小类型与这里的性能问题关系不大。未装箱列表结构也没有。事实上,print
似乎确实比C中的printf
慢得多。但是a)担心打印的性能是相当愚蠢的,因为如果你有那么多的数字,那就很重要了!(对于大数据量,总是使用二进制格式。)……因此,说“Haskell很慢”是极不公平的,只是一个不重要的特定任务很慢。b) 还没那么慢。第五个因素是,许多语言只能梦想在性能真正重要的任务中接近C!根据经验,写得相当好的Haskell比C慢2倍;有时,即使是一个简单的版本也接近C,只要付出一些努力,几乎总是可以实现这一点。我应该指出,两个程序编写的经济成本相同,理解的成本相似,因此“您编写的代码很慢”注释的信息量不大。另一件在我的测试中起了很大作用的事情是:用换行符和all构造完整的字符串,然后对整个字符串调用一次putStr(Ln)
,而不是每行调用一次。在将字符实际转储到输出缓冲区之前,putStrLn
中似乎有一堆前沿问题。可能是<代码> PUBSLN 有类似的问题,你可以得到第二个便宜的赢家——虽然你使用懒惰的字节串,但是内存成本可能是值得考虑的问题!另一件需要考虑的是RTS设置时间。也许仅仅因为RTS需要设置GC等,甚至连main=pure()
都需要一些时间(did n
{-# LANGUAGE OverloadedStrings #-}
import Data.ByteString.Builder
import System.IO (stdout)
import Data.Foldable
main :: IO ()
main = do
traverse_ (hPutBuilder stdout . (<>"\n") . intDec) [(1::Int)..10000000]
./foo 1.05s user 0.13s system 38% cpu 3.048 total
tail -n1 3.02s user 0.01s system 99% cpu 3.047 total