【发布时间】:2016-10-12 05:48:45
【问题描述】:
我正在考虑制作一个适用于无限列表的foldl,以应对无法获得受保护递归但根据第一个参数可能不使用第二个参数的情况。
例如乘法,通常你需要两个参数并且受保护的递归不起作用,但如果第一个参数是 0 你可以短路。
于是我写了如下函数:
foldlp :: (b -> a -> b) -> (b -> Bool) -> b -> [a] -> b
foldlp f p = go where
go b [] = b
go b (x : xs)
| p b = go (f b x) xs
| otherwise = b
并用我的自定义短路乘法函数对其进行了测试:
mult :: Integer -> Integer -> Integer
mult 0 _ = 0
mult x y = x * y
main :: IO ()
main = print . <test_function>
我使用-prof -fprof-auto -O2、+RTS -p 得到的结果是:
foldlp mult (/= 0) 1 $ replicate (10 ^ 7) 1
total time = 0.40 secs
total alloc = 480,049,336 bytes
foldlp mult (`seq` True) 1 $ replicate (10 ^ 7) 1
total time = 0.37 secs
total alloc = 480,049,336 bytes
foldl' mult 1 $ replicate (10 ^ 7) 1
total time = 0.37 secs
total alloc = 480,049,352 bytes
foldl mult 1 $ replicate (10 ^ 7) 1
total time = 0.74 secs
total alloc = 880,049,352 bytes
foldr mult 1 $ replicate (10 ^ 7) 1
total time = 0.87 secs
total alloc = 880,049,336 bytes
这是非常有前途的,因为我的自定义函数允许灵活的严格类型,并且也适用于无限列表
第一个示例将在遇到0 时立即终止,foldr 也将终止,但foldr 的速度要慢得多。
它避免了元组内的 thunk 等问题,因为((1 + 2) + 3, (10 + 20) + 30) 在技术上属于 WHNF,从而破坏了foldl'。
您可以通过flip foldl (const True) 和foldl' 重新获得foldl 和flip foldl (seqTrue)。并且通过这样做似乎重新获得了原始受限函数的性能特征。
所以作为旁注,我认为foldlp 将是对Foldable 的一个值得补充。
但我的实际问题是,为什么当我添加 {-# INLINE foldlp #-} 时,函数性能会显着下降,这给了我:
foldlp mult (/= 0) 1 $ replicate (10 ^ 7) 1
total time = 0.67 secs
total alloc = 800,049,336 bytes
所以我真正的问题是为什么会这样。我认为内联的缺点是代码膨胀,对运行时性能和内存使用量增加没有显着的负面影响。
【问题讨论】:
-
看起来强制内联提前发生会导致其他一些规则无法正常触发......