当使用 -N (并行)标志运行时,GHC 在做什么?

Fre*_*Vds 5 parallel-processing multithreading haskell ghc

我编写了以下测试应用程序:

main = print $ sum $ map (read . show) [1 .. 10^7]
Run Code Online (Sandbox Code Playgroud)

当我使用和不使用 -N 标志运行它时,我得到以下结果:

$ ghc -O2 -threaded -rtsopts -o test test.hs
...
$ time ./test +RTS -s
50000005000000
real    0m12.411s
user    0m12.367s
sys     0m0.040s
$ time ./test +RTS -s -N12
50000005000000
real    0m22.702s
user    1m14.904s
sys     0m12.608s
Run Code Online (Sandbox Code Playgroud)

似乎 GHC 决定通过将计算分布在不同的核心上来尊重 -N12 标志(结果非常糟糕),但我找不到任何有关当代码不包含显式指令时它如何决定这样做的文档。我缺少一些文档吗?

我有 GHC 版本 8.6.5。

垃圾收集统计:

main = print $ sum $ map (read . show) [1 .. 10^7]
Run Code Online (Sandbox Code Playgroud)

K. *_*uhr 5

GHC 不会自动并行化代码。(运行时系统本身可能会利用多个线程进行初始化,从而在启动时提供小的、固定的性能改进,但这是唯一“自动”发生的事情。)

因此,您的代码是按顺序运行的。正如一些评论中指出的,奇怪的性能问题可能是并行垃圾收集。

据观察,当运行大量功能时,并行 GC 在某些工作负载上的性能非常差。例如,请参阅问题#14981 。当然,这个问题讨论的是 32 或 64 核机器。

然而,我观察到性能非常差,尤其是在默认运行时 GC 设置下,即使在数量相对较少的内核上也是如此。例如,使用您的测试用例和 GHC 版本,我在具有-N12或更多配置的 8 核、16 线程英特尔 i9-9980HK 笔记本电脑上得到类似的较差性能。以下是 1 功能和 12 功能运行的比较。编译它:

$ cat test.hs
main = print $ sum $ map (read . show) [1 .. 10^7]
$ stack ghc --resolver=lts-14.27 -- -fforce-recomp -O2 -threaded -rtsopts -o test test.hs
[1 of 1] Compiling Main             ( test.hs, test.o )
Linking test ...
Run Code Online (Sandbox Code Playgroud)

在一种功能上运行它:

$ time ./test +RTS -N1
50000005000000

real    0m10.803s
user    0m10.770s
sys     0m0.037s
Run Code Online (Sandbox Code Playgroud)

十二点运行:

$ time ./test +RTS -N12
50000005000000

real    0m15.655s
user    0m52.103s
sys     0m7.019s
Run Code Online (Sandbox Code Playgroud)

要查看并行 GC 的错误,我们可以切换到顺序 GC:

$ time ./test +RTS -N12 -qg
50000005000000

real    0m11.175s
user    0m11.066s
sys     0m0.120s
Run Code Online (Sandbox Code Playgroud)

我曾假设这种糟糕的并行 GC 性能与超过物理核心数量有关,但您的经验表明,即使不超过物理核心数量,大约 12 个功能也可能会发生这种情况。

建议您使用运行时垃圾收集器控件,而不是完全禁用并行 GC 。其影响可能是惊人的。例如,将第 0 代分配区域从默认的 1m 增加到 4m 会带来很大的改进:

$ time ./test +RTS -N12 -A4m
50000005000000

real    0m12.485s
user    0m25.219s
sys     0m2.053s
Run Code Online (Sandbox Code Playgroud)

甚至更高到 16m 就完全消除了性能问题,至少对于这个简单的测试用例来说是这样。

$ time ./test +RTS -N12 -A16m
50000005000000

real    0m11.481s
user    0m11.775s
sys     0m0.126s
Run Code Online (Sandbox Code Playgroud)

我在第二代切换到压缩时得到了类似的改进:

$ time ./test +RTS -N12 -c
50000005000000

real    0m11.125s
user    0m11.043s
sys     0m0.089s
Run Code Online (Sandbox Code Playgroud)

当然,在减少数量的核心上运行并行 GC 也可能有所帮助:

$ time ./test +RTS -N12 -qn4
50000005000000

real    0m14.092s
user    0m18.961s
sys     0m3.031s
Run Code Online (Sandbox Code Playgroud)