过滤器foo的内存使用情况[2..n] !! 0

时间:2015-03-26 03:36:33

标签: haskell ghc

假设我有以下功能

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;
        }
    }
}

2 个答案:

答案 0 :(得分:5)

GHC 7.10和7.8.4(我测试的版本)非常智能,可以跳转到优化的示例。

我从互联网上查找了两个素数,15485863和67867967.当我编译并运行带有main = print $ small_div 15485863标志的+RTS -s时,我的总堆分配为51 Kb。当我用67867967运行时,我也获得了51 Kb的分配。这意味着没有为列表的生成和过滤分配单元格。

很可能通过所谓的foldr/build融合进行优化(filterenumFromTo 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列表 已经消失了,这意味着,如果我的理解是正确的,懒惰将确保此函数最多只分配一个列表单元格。