给定程序:
import Debug.Trace
main = print $ trace "hit" 1 + trace "hit" 1
如果我使用
ghc -O
(7.0.1或更高版本)进行编译,则会得到输出:hit
2
即GHC使用通用子表达式消除(CSE)将我的程序重写为:
main = print $ let x = trace "hit" 1 in x + x
如果我使用
-fno-cse
进行编译,那么我看到hit
出现了两次。通过修改程序可以避免CSE吗?是否有任何我可以保证
e
不会被CSE拒绝的子表达式e + e
?我知道 lazy
,但是找不到旨在抑制CSE的任何东西。这个问题的背景是cmdargs库,其中CSE破坏了该库(由于库中的杂质)。一种解决方案是要求该库的用户指定
-fno-cse
,但我希望修改该库。 最佳答案
如何通过使用引入后果的排序单子(monad)消除麻烦的根源-隐性后果?例如。具有跟踪的严格身份monad:
data Eval a = Done a
| Trace String a
instance Monad Eval where
return x = Done x
Done x >>= k = k x
Trace s a >>= k = trace s (k a)
runEval :: Eval a -> a
runEval (Done x) = x
track = Trace
现在,我们可以按照
trace
调用的顺序保证编写内容:main = print $ runEval $ do
t1 <- track "hit" 1
t2 <- track "hit" 1
return (t1 + t2)
虽然仍然是纯代码,但即使使用
-O2
,GHC也不会尝试变得聪明: $ ./A
hit
hit
2
因此,我们仅介绍足以告诉GHC我们想要的语义的计算效果(跟踪)。
这对于编译优化非常可靠。如此之多,以至于GHC在编译时将数学优化为
2
,但仍然保留trace
语句的顺序。为了证明这种方法的鲁棒性,以下是
-O2
和积极的内联的核心:main2 =
case Debug.Trace.trace string trace2 of
Done x -> case x of
I# i# -> $wshowSignedInt 0 i# []
Trace _ _ -> err
trace2 = Debug.Trace.trace string d
d :: Eval Int
d = Done n
n :: Int
n = I# 2
string :: [Char]
string = unpackCString# "hit"
因此,GHC竭尽所能优化代码(包括静态计算数学),同时仍保留正确的跟踪信息。
引用:Simon Marlow介绍了用于排序的有用
Eval
monad。关于optimization - 如何使用GHC防止常见的子表达消除(CSE),我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/5920200/