假设我有以下功能
small_div :: Int -> Int
small_div n = filter (\x -> n `rem` x == 0) [2..n] !! 0
这个功能的内存使用情况是多少?等效的C代码将是恒定的内存使用量,我相信Haskell的懒惰评估意味着它不会创建[2..n]的更多元素而不是找到第一个除数所需的元素,但是ghc足够聪明,可以跳到像...这样的东西。
int small_div(int n) {
for (int x = 2; x <= n; x++) {
if (n % x == 0) {
return x;
}
}
}
答案 0 :(得分:5)
GHC 7.10和7.8.4(我测试的版本)非常智能,可以跳转到优化的示例。
我从互联网上查找了两个素数,15485863和67867967.当我编译并运行带有main = print $ small_div 15485863
标志的+RTS -s
时,我的总堆分配为51 Kb。当我用67867967运行时,我也获得了51 Kb的分配。这意味着没有为列表的生成和过滤分配单元格。
很可能通过所谓的foldr/build
融合进行优化(filter
和enumFromTo
Int
- s都参与了这种融合。
答案 1 :(得分:3)
为了查看GHC高级优化的结果,在简化阶段之后,比汇编器输出更好的选择是GHC的内部核心语言。 它需要学习阅读,但应该比程序集短得多。我只是一个了解核心的初学者,但让我试着展示一下。
哦,因为我使用的是Haskell平台,所以这是GHC 7.8.3。
给定文件
module Test where
small_div :: Int -> Int
small_div n = filter (\x -> n `rem` x == 0) [2..n] !! 0
如果我们使用 no 优化选项进行编译,并使用all转储核心输入 &#34;额外&#34;信息被抑制(这会删除批次的分析和类型信息,只留下必要的结构):
ghc -dsuppress-all -ddump-simpl Test.hs
我们得到的定义为
small_div =
\ n_apH ->
!!
(filter
(\ x_arI -> == $fEqInt (rem $fIntegralInt n_apH x_arI) (I# 0))
(enumFromTo $fEnumInt (I# 2) n_apH))
(I# 0)
这基本上是将Haskell直接翻译成核心,仍然包含所有列表构造。核心的语法比Haskell简单得多,甚至运算符都使用前缀。另一方面,许多内部GHC详细信息现在正在窥视,例如I#
的{{1}}构造函数和用于比较Int
的{{1}}单态函数。
请注意,即使在这种形式中,懒惰和垃圾收集应该意味着它可以在恒定的空间中运行,这可能意味着(意思是我&#34; m&#34;受过教育的&#34;在这里猜测)完全保持GHC的高效GC&#34;托儿所&#34;生成短期数据。
现在,如果我们将$fEqInt
基本优化选项添加到GHC,则输出会获得 lot 更棘手(甚至更糟糕的是Int
)。 -O
和-O2
都已消失,已被列表融合优化删除并且内联结果。此外,大多数算法现在适用于未装箱的filter
。以下是enumFromTo
输出的所有荣耀:
Int#
然而,即使在所有这些融合和内联之后,它仍然包含-O
列表构造函数。并注意那里的单行,给出最大函数的通常最终结果:
small_div2
small_div2 = I# (-1)
small_div1
small_div1 = !!_sub ([]) 0
$wsmall_div
$wsmall_div =
\ ww_s1dD ->
case tagToEnum# (># 2 ww_s1dD) of _ {
False ->
letrec {
a_s1e8
a_s1e8 =
case ww_s1dD of _ {
__DEFAULT -> go_a1ch 0;
(-1) -> []
};
lvl_s1e1
lvl_s1e1 = : small_div2 a_s1e8;
go_a1ch
go_a1ch =
\ x_a1ci ->
case x_a1ci of wild1_a1aq {
__DEFAULT ->
case remInt# ww_s1dD wild1_a1aq of _ {
__DEFAULT ->
case tagToEnum# (==# wild1_a1aq ww_s1dD) of _ {
False -> go_a1ch (+# wild1_a1aq 1);
True -> []
};
0 ->
: (I# wild1_a1aq)
(case tagToEnum# (==# wild1_a1aq ww_s1dD) of _ {
False -> go_a1ch (+# wild1_a1aq 1);
True -> []
})
};
(-1) -> lvl_s1e1;
0 -> case divZeroError of wild2_00 { }
}; } in
!!_sub (go_a1ch 2) 0;
True -> small_div1
}
small_div
small_div =
\ w_s1dA ->
case w_s1dA of _ { I# ww1_s1dD -> $wsmall_div ww1_s1dD }
外部列表的构造及其:
索引是而不是被优化掉了。 (虽然我不包括 !!_sub (go_a1ch 2) 0;
输出,但它仍然适用。)
然而,中间!! 0
列表 已经消失了,这意味着,如果我的理解是正确的,懒惰将确保此函数最多只分配一个列表单元格。