Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/haskell/10.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Performance 为什么这个简单的Haskell程序这么慢?_Performance_Haskell - Fatal编程技术网

Performance 为什么这个简单的Haskell程序这么慢?

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

在这个打印从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 <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
    ,这是一个字符链接列表
  • UTF编码
  • RTS差异
  • 对于1,使用文本的压缩变体的性能没有太大差异,可能是由于问题2

    对于2,ByteString变量(压缩字节而不是字符)更能代表C程序的功能:

    -- Using functions from the Relude package
    main = traverse_ putBSLn (show <$> [(1::Int)..10000000])
    
    因此,CPU时间更接近C程序,这让我假设这种差异主要是由于Haskell的前奏曲中默认使用的例程中内置了不必要的UTF8处理

    死胡同:

    • 我尝试了
      NoBuffering
      和large
      BlockBuffering
      ,但没有成功
    • 构造一个大的bytestring并用一个调用进行打印并没有任何好处(懒惰或严格的bytestring)
    • 通过
      文本
      而不是
      字符串
      打印只得到了最轻微的改进
    • 直接呈现到ByteString,而不是将值
      show
      ed打包为字符串。如果做得好,我想这可能是一场胜利
    编辑:我简直不敢相信我忘记了Builder,它是一种优化的方法来构建ByTestring,在某些情况下,它可以很好地融合以减少分配。Builder是我已经展示的上述示例的基础,但直接使用它可以进行一些手动优化

    {-# 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
    over
    Integer
    。还要注意,两种计时都包括两种不同的内容:生成值的计算和打印值的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