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.但是,它不起作用.
概括.如果你有一个常量值,你可以将它推广到某个变量的函数吗?我的问题函数的命名,twoTrues立即表明,这是不变序列中的第三zeroTrues,oneTrue,twoTrues,threeTrues等等-的确是.因此,推广twoTrues到一个nTrues 带参数n和删除的函数,twoTrues将从程序中消除一个CAF.
因为它发生,在这种情况下,我只考虑了案件zeroTrues,oneTrue并twoTrues为我的计划,因为这是我需要的,但我的程序可以自然地扩展以处理nTrues为n> 2 -所以要概括,以nTrues将意味着它将使意义"概括了所有的方式"来的用户zeroTrues,oneTrue等等.这不会永远是这样.
注意:可能仍有其他CAF需要处理,无论是在代码中,还是由GHC的"优化"(在这些病态情况下都不是真正的优化)产生的.
然而,这个答案可能涉及程序员的更多工作,而不是严格必要的工作.正如Don的回答所显示的那样,实际上没有必要概括.
另一方面,在某些情况下,推广常量可以使您更清楚您实际在做什么,并帮助重用.它甚至可以揭示以更好的系统方式和/或更有效地计算一系列值的方法.
关于这个特殊情况的说明(可以忽略):在这种特殊情况下,我不想让nTrues 自己成为无限列表(这将再次成为CAF,重新引入原始问题!)而不是函数.一个原因是虽然twoTrues可能以无限列表的形式有用,但我无法看到它是如何有用的(在我的应用程序中,无论如何)nTrues以无限列表的形式.
通过引入虚拟参数,您还必须使其看起来实际取决于参数的结果.否则,GHC的聪明才智可能会再次成为CAF.
我建议如下:
twoTrues u = map (++ (True : repeat False)) . trueBlock <$> [(u `seq` 1)..]
Run Code Online (Sandbox Code Playgroud)
这似乎是一个长期存在的问题http://hackage.haskell.org/trac/ghc/ticket/917.在我看来,这是一个关键的问题.