【问题标题】:How to force GHC to evaluate static expression only once如何强制 GHC 只计算一次静态表达式
【发布时间】:2016-11-03 18:52:50
【问题描述】:

我有一个类似 SQL 的简单示例 join 用于有序列表:如果 outer 参数是 True 那么它是联合;否则就是交集:

import System.Environment

main = do
  [arg] <- getArgs
  let outer = arg == "outer"
  print $ length $ joinLists outer [1..1000] [1,3..1000]

joinLists :: (Ord a, Num a) => Bool -> [a] -> [a] -> [a]
joinLists outer xs ys = go xs ys
  where
  go [] _ = []
  go _ [] = []
  go xs@(x:xs') ys@(y:ys') = case compare x y of
    LT -> append x $ go xs' ys
    GT -> append y $ go xs ys'
    EQ -> x : go xs' ys'
  append k = if {-# SCC "isOuter" #-} outer then (k :) else id

当我分析它时,我看到每次调用 append 时都会评估 isOuter 条件:

stack ghc -- -O2 -prof example.hs && ./example outer +RTS -p && cat example.prof 

                                                     individual      inherited
COST CENTRE MODULE                no.     entries  %time %alloc   %time %alloc
MAIN        MAIN                   44          0    0.0   34.6     0.0  100.0
 isOuter    Main                   88        499    0.0    0.0     0.0    0.0

但我希望条件只被评估一次,所以go 循环中的append 被替换为(k :)id。我可以以某种方式强迫它吗?跟记忆有关吗?

编辑:好像我误解了探查器的输出。我在append 定义中添加了跟踪:

append k = if trace "outer" outer then (k :) else id

outer 只打印一次。

EDIT2:如果我用无点定义替换append,那么if 条件只评估一次:

 append = if outer then (:) else flip const

【问题讨论】:

  • 我试过{-# NOINLINE append #-},没有效果。
  • 我认为您误读了分析器的输出。您的成本中心是 if 语句的条件,每次调用 append 时都会对其进行评估,因此会点击成本中心。但是 outer 变量指向的 thunk 只被评估一次。如果您将成本中心放在arg == "outer" 中,那么您应该会看到它只被点击一次。
  • 如果您不想多次评估if 条件,您可以手动将其浮动到循环外部。 joinLists outer = go (if outer then (:) else flip const) 并重新定义 goappend 提供一个额外的参数,而不是引用它的闭包。我无法预测您的输入是否会更快。
  • 如果你这样定义 append 会发生什么:append = if outer then (:) else flip const? (我会自己测试,但我现在没有 GHC。)
  • @BenjaminHodgson 并感谢您的 cmets,他们实际上回答了我的问题(请参阅编辑)。

标签: haskell optimization


【解决方案1】:

我会尝试向内推 lambda:

append = if {-# SCC "isOuter" #-} outer then \k -> (k :) else \k -> id

原始代码本质上是\k -&gt; if outer ...,它首先接受参数,然后测试守卫。上面的代码在接受参数之前测试了守卫。

替代方案:

append | outer     = \k -> (k :) 
       | otherwise = \k -> id

可以进一步将这些 lambda 简化为更易读的形式。

【讨论】:

  • 顺便说一句,\k -&gt; (k :) 只是(:),但我喜欢与\k -&gt; id 的对称性(而不是const id)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-11-08
  • 1970-01-01
  • 1970-01-01
  • 2019-11-18
  • 2017-06-19
  • 1970-01-01
相关资源
最近更新 更多