【发布时间】:2017-02-15 11:50:19
【问题描述】:
我为 Haskell 的内置 [] 列表数据类型编写了量化函数 exists、forall 和 none。在多种情况下,这些似乎证明比Prelude/Data.Lists any 和all 更有效。我天真地怀疑这种性能是由于 any 和 all 使用 Θ(n) 折叠实现的。由于我对 Haskell 比较陌生,我想我一定是弄错了,或者这种现象是有充分理由的。
来自Data.Foldable:
-- | Determines whether any element of the structure satisfies the predicate.
any :: Foldable t => (a -> Bool) -> t a -> Bool
any p = getAny #. foldMap (Any #. p)
-- | Determines whether all elements of the structure satisfy the predicate.
all :: Foldable t => (a -> Bool) -> t a -> Bool
all p = getAll #. foldMap (All #. p)
我的实现:
exists :: (a -> Bool) -> [a] -> Bool
exists _ [] = False
exists pred (x : xs) | pred x = True
| otherwise = exists pred xs
和
forall pred = not . exists (not . pred)
none pred = not . exists pred = forall (not . pred)
消除布尔反转:
forall, none :: (a -> Bool) -> [a] -> Bool
forall _ [] = True
forall pred (x : xs) | pred x = forall pred xs
| otherwise = False
none _ [] = True
none pred (x : xs) | pred x = False
| otherwise = none pred xs
all:
time 327.8 μs (322.4 μs .. 333.0 μs)
0.997 R² (0.996 R² .. 0.998 R²)
mean 328.7 μs (324.1 μs .. 334.2 μs)
std dev 16.95 μs (14.63 μs .. 22.02 μs)
和forall:
time 113.2 μs (111.2 μs .. 115.0 μs)
0.997 R² (0.996 R² .. 0.998 R²)
mean 112.0 μs (110.0 μs .. 113.9 μs)
std dev 6.333 μs (5.127 μs .. 7.896 μs)
使用标准的nf 衡量性能。
正如预期的那样,我并没有重新发明折叠,而是低估了编译器标志,并且天真地没想到-O2 与默认优化级别性能相比会产生如此巨大的整体差异,也没有个别定制之间的优化效果差异 -写和图书馆的配方。许多高效的专业标准函数优化显然只有在明确启用时才会发挥作用。
Haskell 标记信息的“性能”部分强调了优化级别编译器标志在测试代码效率时的重要性。通常建议相信库函数实现的复杂性,而不是重新连接 RULES 杂注或重新制定基本形式,而是尝试利用已经培养的优化潜力。
【问题讨论】:
-
如果你想推理这个级别的代码性能,你可能应该看看核心。性能差距与渐近无关(怎么可能?您的函数显然也是 O(n)) - 我的猜测是由于
Foldable函数中缺少内联。对于某些f、z和List.foldr,您的所有函数都等效于foldr f z,即使Foldable.foldr没有,也可能内联。 -
我无法用简单的
all/forall重现这个。不出所料,在涉及可熔操作的情况下,这是毫无争议的:sprunge.us/RQdO 当然,您可以为forall和公司编写融合规则。 -
@Michael 我得到的结果与
-O0的 Georg 相似,-O2与你的相似。 -
请阅读haskell标签信息部分关于性能问题
-
你也应该试试
-O。它的编译速度比-O2快得多,而且它生成的代码通常已经足够好了。
标签: performance haskell linked-list fold any