我观察到以下执行时间:
- 使用
stack build
编译时约为9秒 - 使用
stack build --profile
编译时约5秒
我希望未分析的执行时间低于1秒,这说明上面的分析时间是有意义的,是未分析的时间异常慢。
更多细节:
- 该程序读取类似关系代数的DSL,应用一系列基于规则的转换,并将转换结果输出到SQL。解析使用
megaparsec
完成。I/O基于String
,并且相对较小(~ 150 KB)。我会排除I/O作为问题的来源。转换涉及ADT上的递归重写规则。在少数情况下,我使用ugly-memo
来加速这种递归重写。 - 使用带有LTS 18.28、ghc 8.10.7的堆栈2.9.1
(编辑:升级到LTS 20.11、ghc 9.2.5没有帮助)
- 在阴谋集团的档案里:
ghc-options: -O2 -Wall -fno-warn-unused-do-bind -fno-warn-missing-signatures -fno-warn-name-shadowing -fno-warn-orphans
ghc-prof-options: -O2 -fprof-auto "-with-rtsopts=-p -V0.0001 -hc -s"
- 请注意,以上这些都不是新的,但我以前从未观察过这种行为。
- 我看过this related question,但是用
-fno-state-hack
编译没有帮助 - 使用
-O1
编译没有帮助(与-O2
差不多),并且-O0
明显更慢,正如预期的那样。 - 分析信息显示没有问题。只有在未分析的执行时才会出现此问题。
我知道我没有提供太多细节。特别是,我没有包括任何代码片段,因为它是一个很大的代码库,我不知道它的哪一部分可以触发这种行为。我的意思是,我确实不知道如何缩小它。
所以我的问题不是“问题出在哪里",而是:考虑到显而易见的工具(分析)在这种情况下是无用的,我能做些什么来更接近问题的根源呢?
2条答案
按热度按时间zujrkrfu1#
虽然我仍然不知道概要文件和非概要文件执行之间的差异的确切原因,但现在我在代码中找到了罪魁祸首。
在ADT上递归的函数实现如下:
请注意:
Env
参数,这个参数在内部使用,但在整个递归过程中不会改变。注意,它被声明为隐式参数。关于这个函数的分析信息显示了比预期更多的函数调用。比预期多了三个数量级!(尽管如此,仍然很奇怪,分析执行相当快)。
结果,隐式参数是个问题,我不知道确切的原因,但我读过关于隐式参数有多邪恶的博客,现在我有了第一手的证据。
将此函数更改为仅使用显式参数完全修复了此问题:
隐式参数似乎导致uglymemo无法识别先前存储的调用,因此我得到了所有随之而来的开销,但没有任何好处。
我的问题算是解决了,但是如果有人能解释隐式参数导致这种行为的潜在原因,以及为什么概要执行会更快,我仍然非常有兴趣了解这一点。
ux6nzvsh2#
这听起来很矛盾:"所以我的问题不是"问题出在哪里",而是:我能做些什么来更接近问题的根源"
在调试器下运行它,在9或5的时候手动暂停它。这样做几次。堆栈会告诉你它是如何浪费时间的,不管是否是I/O。这就是this technique.
你有什么可失去的?