当前位置: 首页 > 知识库问答 >
问题:

为什么这个简单的Haskell程序这么慢?[重复]

燕翔飞
2023-03-14

在这个打印从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

共有1个答案

艾鹭洋
2023-03-14

在我的系统中,您的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)的区别。

哈斯凯尔之所以迟钝,部分原因是(其中一些是假设)

  1. 默认情况下,print和底层的putStrLn使用String,这是一个字符链接列表
  2. UTF编码
  3. RTS差异

对于1,使用文本的压缩变体的性能没有太大不同,可能是因为问题2。

对于2,ByteString变体(打包字节而不是字符)更能代表您的C程序正在做的事情:

-- Using functions from the Relude package
main = traverse_ putBSLn (show <$> [(1::Int)..10000000])

结果在

10000000
./foo  1.55s user 0.08s system 56% cpu 2.904 total

因此,CPU时间更接近于您的C程序,这使我假设这种差异主要是由于Haskell前奏中默认使用的例程中内置的不必要的UTF8处理。

死胡同:

  • 我尝试了NoBuffering和largeBlockBuffering,但没有成功
  • 构造一个大的bytestring并用一个调用进行打印并没有任何好处(懒惰或严格的bytestring)
  • 通过文本而不是字符串打印只得到了最轻微的改进
  • 直接呈现到ByteString,而不是将值showed打包成字符串。如果做得好,我想这可能是一场胜利

编辑:我不敢相信我忘记了Builder,这是一种构建字节串的优化方法,在某些情况下,它融合得很好,可以减少分配。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]

表演地点:

./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

实际上,这比之前对hPut的许多单独调用更有效,因为正如hPutBuilder所说:

这个函数比hPut更有效。toLazyByteString,因为在许多情况下不需要进行缓冲区分配。此外,短构建器的多个执行的结果被连接到句柄缓冲区中,因此避免了不必要的缓冲区刷新。

所以我应该补充:4。在这种情况下,Haskell的速度很慢,因为有时计算不会融合,最终会得到多余的分配,这是不免费的。

 类似资料:
  • 对于特定的任务,我需要在可变数组中进行大量快速、单独的写操作。为了检查性能,我使用了以下测试:

  • 我试图制作一个基本的C程序来读取一个文件,但由于某种原因,当我使用运行它时<代码>/Test1 Test1。txt我得到“错误:'s'可能在未初始化的情况下用于此函数”。

  • 问题内容: 这是所有编程语言所共有的吗?在进行多次打印后再执行println似乎更快,但是将所有内容移动到字符串中并仅进行打印似乎最快。为什么? 编辑:例如,Java可以在不到一秒钟的时间内找到所有高达100万的质数- 但要进行打印,然后在自己的println中将它们全部输出可能需要几分钟!最多可打印100亿小时! 例如: 问题答案: 速度并不慢,而是由主机操作系统提供的与控制台连接的基础。 您可

  • 问题内容: 我对此感到困惑 现在让我们来看看numpy: 神圣的CPU周期蝙蝠侠! 使用改进,但恕我直言仍然不够 numpy.version.version =‘1.5.1’ 如果您想知道在第一个示例中是否跳过了列表创建以进行优化,则不是: 问题答案: Numpy已针对大量数据进行了优化。给它一个很小的3长度数组,毫不奇怪,它的性能很差。 考虑单独的测试 输出是 似乎是数组的归零一直花费在nump

  • 问题内容: Magento通常这么慢吗? 这是我的第一次使用体验,管理面板只需花一些时间即可加载和保存更改。这是带有测试数据的默认安装。 托管该服务器的服务器可超快地服务于其他非Magento站点。Magento使它如此缓慢的PHP代码有什么用,该如何解决? 问题答案: 我只是切身参与优化Magento的性能,但这是系统速度如此缓慢的一些原因 Magento的某些部分使用在MySQL之上实现的EA

  • 问题内容: 在有人质疑使用的事实之前,我先说一下,出于内存和性能的原因,我需要在特定的应用程序中使用它。[1] 因此,到目前为止,我一直使用并假定这是最有效的方法。但是,自古以来我就注意到它是软件的瓶颈。[2] 然后,就在最近,我试图用一个巨大的映射替换,在该映射中放置/获取字符串,以便每次获得唯一的实例。我以为这会慢一些…但是事实恰恰相反!它快得多了!通过推送/轮询地图(实现完全相同)来替换,可