如何在Haskell中使CAF不是CAF?

Rob*_*een 27 haskell ghc compiler-optimization

如何将常量适用表格制作成一个不变的适用表格,以阻止它在程序的整个生命周期中保留?

我尝试过这种方法:

-- | Dummy parameter to avoid creating a CAF
twoTrues :: () -> [[[Bool]]]
twoTrues _ = map (++ (True : repeat False)) . trueBlock <$> [1..]
Run Code Online (Sandbox Code Playgroud)

但它似乎不起作用 - 配置文件显示它仍然保留,仍然将其标记为CAF.

我发现了一个相关的Google结果,Simon Peyton-Jones对Neil Mitchell 的回复,他正好问了这个问题 - 但不幸的是,答案指的是一个死链接.

Don*_*art 15

一个完整的例子

这是一个显示情况的小例子:

module A where

big :: () -> [Int]
big _ = [1..10^7]
Run Code Online (Sandbox Code Playgroud)

看起来像一个功能,对吗?但是GHC做了什么?它将枚举浮动到顶层!

A.big1 :: [Int]
[ Unf=Unf{Src=<vanilla>, TopLvl=True, Arity=0, Value=False,
         ConLike=False, Cheap=False, Expandable=False,
         Guidance=IF_ARGS [] 7 0}]
A.big1 =
  case A.$wf1 10 A.big2 of ww_sDD { __DEFAULT ->
  eftInt 1 ww_sDD
  }

A.big :: () -> [Int]
[Arity=1,    
 Unf=Unf{Src=InlineStable, TopLvl=True, Arity=1, Value=True,
         ConLike=True, Cheap=True, Expandable=True,
         Guidance=ALWAYS_IF(unsat_ok=True,boring_ok=True)
         Tmpl= \ _ -> A.big1}]
A.big = \ _ -> A.big1
Run Code Online (Sandbox Code Playgroud)

哎呀!


所以,我们能做些什么?

关闭优化

这有效,-Onot但不可取:

A.big :: () -> [Int]
[GblId, Arity=1]
A.big =
  \ _ ->
    enumFromTo
      @ Int
      $fEnumInt
      (I# 1)
      (^
         @ Int
         @ Type.Integer
         $fNumInt
         $fIntegralInteger
         (I# 10)
         (smallInteger 7))
Run Code Online (Sandbox Code Playgroud)

不要内联,更多功能

所有内容都变成一个函数,包括enumFromTo将参数传递给worker:

big :: () -> [Int]
big u = myEnumFromTo u 1 (10^7)
{-# NOINLINE big #-}

myEnumFromTo :: () -> Int -> Int -> [Int]
myEnumFromTo _ n m = enumFromTo n m
{-# NOINLINE myEnumFromTo #-}
Run Code Online (Sandbox Code Playgroud)

现在我们终于无CAF了!即使-O2

A.myEnumFromTo [InlPrag=NOINLINE]
  :: () -> Int -> Int -> [Int]
A.myEnumFromTo =
  \ _ (n_afx :: Int) (m_afy :: Int) ->
    $fEnumInt_$cenumFromTo n_afx m_afy

A.big [InlPrag=NOINLINE] :: () -> [Int]
A.big = \ (u_abx :: ()) -> A.myEnumFromTo u_abx A.$s^2 lvl3_rEe
Run Code Online (Sandbox Code Playgroud)

好极了.


什么不起作用?

关掉 - 懒惰

完整的懒惰转换向外浮动定义.默认情况下启用-O1或者以上.我们试着把它关掉-fno-full-laziness.但是,它不起作用.

  • 我该如何_use_`big()`?如果我写'main = do {print $ length $ big(); print $ length $ big()}`,公共子表达式消除应用于表达式`big()`并且它浮动到顶层(根据GHC 7.0.3生成的核心),并且程序需要一个大量的空间,使整个努力无效,以避免在你的答案中分享. (3认同)

Rob*_*een 8

概括.如果你有一个常量值,你可以将它推广到某个变量的函数吗?我的问题函数的命名,twoTrues立即表明,这是不变序列中的第三zeroTrues,oneTrue,twoTrues,threeTrues等等-的确是.因此,推广twoTrues到一个nTrues 带参数n和删除的函数,twoTrues将从程序中消除一个CAF.

因为它发生,在这种情况下,我只考虑了案件zeroTrues,oneTruetwoTrues为我的计划,因为这是我需要的,但我的程序可以自然地扩展以处理nTruesn> 2 -所以要概括,以nTrues将意味着它将使意义"概括了所有的方式"来的用户zeroTrues,oneTrue等等.这不会永远是这样.

注意:可能仍有其他CAF需要处理,无论是在代码中,还是由GHC的"优化"(在这些病态情况下都不是真正的优化)产生的.

然而,这个答案可能涉及程序员的更多工作,而不是严格必要的工作.正如Don的回答所显示的那样,实际上没有必要概括.

另一方面,在某些情况下,推广常量可以使您更清楚您实际在做什么,并帮助重用.它甚至可以揭示以更好的系统方式和/或更有效地计算一系列值的方法.

关于这个特殊情况的说明(可以忽略):在这种特殊情况下,我不想让nTrues 自己成为无限列表(这将再次成为CAF,重新引入原始问题!)而不是函数.一个原因是虽然twoTrues可能以无限列表的形式有用,但我无法看到它是如何有用的(在我的应用程序中,无论如何)nTrues以无限列表的形式.


Hei*_*mus 5

通过引入虚拟参数,您还必须使其看起来实际取决于参数的结果.否则,GHC的聪明才智可能会再次成为CAF.

我建议如下:

twoTrues u = map (++ (True : repeat False)) . trueBlock <$> [(u `seq` 1)..]
Run Code Online (Sandbox Code Playgroud)


iva*_*vič 5

这似乎是一个长期存在的问题http://hackage.haskell.org/trac/ghc/ticket/917.在我看来,这是一个关键的问题.

  • 然而,这是一个开放的研究问题. (3认同)
  • 遗憾的是,我们必须与优化器作斗争并进行试错(修改代码,编译,查看核心,重复直到获得所需的核心)来避免这个问题。 (2认同)