【发布时间】: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)并重新定义go为append提供一个额外的参数,而不是引用它的闭包。我无法预测您的输入是否会更快。 -
如果你这样定义 append 会发生什么:
append = if outer then (:) else flip const? (我会自己测试,但我现在没有 GHC。) -
@BenjaminHodgson 并感谢您的 cmets,他们实际上回答了我的问题(请参阅编辑)。
标签: haskell optimization