我想知道在多大程度上我必须担心在Haskell(使用GHC)中微观优化常见的子表达式,特别是在数据解构方面。
例如,请考虑使用此代码合并两个有序列表:
merge :: Ord a => [a] -> [a] -> [a]
merge [] bs = bs
merge as [] = as
merge (a:as) (b:bs) =
case compare a b of
LT -> a : merge as (b:bs) -- (1)
EQ -> a : merge as bs
GT -> b : merge (a:as) bs -- (2)
GHC是否会认识到(1)处的(a:as)
和(1)处的(b:bs)
与输入参数相同?或者,我应该使用"作为"模式,例如:
merge a'@(a:as) b'@(b:bs) =
...
LT -> a : merge as b'
...
GT -> b : merge a' bs
一般情况下,GHC能否识别数据结构中的常见子表达式,何时需要帮助它?
答案 0 :(得分:7)
这取决于。如果您在没有优化的情况下进行编译,那么不使用as-patterns肯定会受到惩罚,但如果您编译以下代码
module Test where
merge1 :: Ord a => [a] -> [a] -> [a]
merge1 [] bs = bs
merge1 as [] = as
merge1 (a:as) (b:bs) = case compare a b of
LT -> a : merge1 as (b:bs)
EQ -> a : merge1 as bs
GT -> b : merge1 (a:as) bs
merge2 :: Ord a => [a] -> [a] -> [a]
merge2 [] bs = bs
merge2 as [] = as
merge2 xs@(a:as) ys@(b:bs) = case compare a b of
LT -> a : merge2 as ys
EQ -> a : merge2 as bs
GT -> b : merge2 xs bs
-O2
和-ddump-simpl
,以及很多明智的编辑,重命名变量,剥离元数据和注释,以及其他各种技巧,你可以得到一些东西像
merge2_2 :: Ord a => a -> [a] -> [a] -> [a]
merge2_2 = \ ordInst x y z -> case z of _ {
[] -> (x:y);
(w:v) -> case compare ordInst x w of _ {
LT -> x : merge2_1 ordInst y w v;
EQ -> x : merge2 ordInst y v;
GT -> w : merge2_2 ordInst x y v
}
}
merge2_1 :: Ord a => [a] -> a -> [a] -> [a]
merge2_1 = \ ordInst x y z -> case x of _ {
[] -> (y:z);
(w:v) -> case compare ordInst w y of _ {
LT -> w : merge2_1 ordInst v y z;
EQ -> w : merge2 ordInst v z;
GT -> y : merge2_2 ordInst w v z
}
}
merge2 :: Ord a => [a] -> [a] -> [a]
merge2 = \ ordInst x y -> case x of wild {
[] -> y;
(w:v) -> case y of _ {
[] -> wild;
(z:zs) -> case compare ordInst w z of _ {
LT -> w : merge2_1 ordInst v z zs;
EQ -> w : merge2 ordInst v zs;
GT -> z : merge2_2 ordInst w v zs
}
}
}
merge1_2 :: Ord a => a -> [a] -> [a] -> [a]
merge1_2 = \ ordInst x y z -> case z of _ {
[] -> (x:y);
(w:v) -> case compare ordInst x w of _ {
LT -> x : merge1_1 ordInst y w v;
EQ -> x : merge1 ordInst y v;
GT -> w : merge1_2 ordInst x y v
}
}
merge1_1 :: Ord a => [a] -> a -> [a] -> [a]
merge1_1 = \ ordInst x y z -> case x of _ {
[] -> (y:z);
(w:v) -> case compare ordInst w y of _ {
LT -> w : merge1_1 ordInst v y z;
EQ -> w : merge1 ordInst v z;
GT -> y : merge1_2 ordInst w v z
}
}
merge1 :: Ord a => [a] -> [a] -> [a]
merge1 = \ ordInst x y -> case x of wild {
[] -> y;
(w:v) -> case y of _ {
[] -> wild;
(z:zs) -> case compare ordInst w z of _ {
LT -> w : merge1_1 ordInst v z zs;
EQ -> w : merge1 ordInst v zs;
GT -> z : merge1_2 ordInst w v zs
}
}
}
与diffing工具进行比较后,告诉我这两个定义之间的唯一区别是merge
函数名末尾的数字。所以是的,在应用优化之后,GHC可以为您找出这些模式,至少在列表的情况下。但是,我仍然建议使用它们,因为总有边缘情况,如果你使用它们,那么你不必相信编译器做一些复杂的事情。这也意味着即使没有启用优化,您的代码仍然会在那里进行优化。这似乎是一个微小的变化,但如果这个功能最终在内部循环或你正在进行的结构非常大,它可能会产生重大的性能影响。另外,我发现对于具有大量字段的构造函数来说,通常更方便的是有一个名称来引用"构造的"形式。
Here's the full core dump if you're interested。基本上,我开始通过连接线来占用更少的空间,然后删除类型签名中的不必要的噪音,删除合格的模块名称,所有[Something]
注释,重命名为merge2_$smerge2
之类的内容到{{1删除所有本地类型签名,然后使用Sublime Text的精彩多个游标功能开始重命名。我从类型名称开始,将它们全部重命名为merge2_2
,然后将变量名称重命名为a
,x
,y
,z
,{{1我和w
(我是创意,不是吗?),制作了v
运算符中缀的所有应用,删除了zs
个区域,以及稍后我会得到你在上面看到的输出。