gpt4 book ai didi

haskell - 为什么添加 INLINE 会减慢我的程序

转载 作者:行者123 更新时间:2023-12-04 18:59:56 26 4
gpt4 key购买 nike

我正在考虑制作 foldl适用于无限列表,适用于您无法获得 protected 递归但根据第一个参数可能不会使用第二个参数的情况。

例如乘法,通常你需要两个参数并且保护递归不起作用,但是如果第一个参数是 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' .

您可以重新获取 foldlflip foldl (const True)foldl'flip foldl (序列 True) .而原来受限功能的性能特征似乎是通过这样做重新获得的。

所以作为旁注,我认为 foldlp将是对 Foldable 的一个有值(value)的补充.

但我的实际问题是为什么当我添加 {-# INLINE foldlp #-} 时功能性能显着下降,给我:
foldlp mult (/= 0) 1 $ replicate (10 ^ 7) 1
total time = 0.67 secs
total alloc = 800,049,336 bytes

所以我真正的问题是为什么会发生这种情况。我认为内联的缺点是代码膨胀,对运行时性能和内存使用量的增加没有显着的负面影响。

最佳答案

根据 the GHC docs INLINE pragma 会阻止其他编译器优化,以便仍然让重写规则生效。

所以我的猜测是,通过使用 INLINE您删除了一些优化,GHC 会应用这些优化来使您的代码更快。

在内核中进行一些探索(在编译中使用 -ddump-simpl)后,我找到了 GHC 执行的优化。为此,我查看了 foldlp 的核心。有内联和无内联:

内联:

foldlp =
\ (@ b_a10N)
(@ a_a10O)
(eta_B2 :: b_a10N -> a_a10O -> b_a10N)
(eta1_B1 :: b_a10N -> Bool)
(eta2_X3 :: b_a10N)
(eta3_X5 :: [a_a10O]) ->
letrec {
go_s1Ao [Occ=LoopBreaker] :: b_a10N -> [a_a10O] -> b_a10N
[LclId, Arity=2, Str=DmdType <L,U><S,1*U>]
go_s1Ao =
\ (b1_avT :: b_a10N) (ds_d1xQ :: [a_a10O]) ->
-- Removed the actual definition of go for brevity,
-- it's the same in both cases
}; } in
go_s1Ao eta2_X3 eta3_X5

非内联:
foldlp =
\ (@ b_a10N)
(@ a_a10O)
(f_avQ :: b_a10N -> a_a10O -> b_a10N)
(p_avR :: b_a10N -> Bool) ->
letrec {
go_s1Am [Occ=LoopBreaker] :: b_a10N -> [a_a10O] -> b_a10N
[LclId, Arity=2, Str=DmdType <L,U><S,1*U>]
go_s1Am =
\ (b1_avT :: b_a10N) (ds_d1xQ :: [a_a10O]) ->
-- Removed the actual definition of go for brevity,
-- it's the same in both cases
}; } in
go_s1Am

相关的区别在最后一行。优化器取消了实际必须调用 foldlp 的步骤。调用 go并且只是从 foldlp 中创建一个带有两个参数的函数,该函数返回一个带有两个参数的函数。内联不会执行此优化,并且核心看起来与您编写的代码完全相同。

我通过编写 foldlp 的三个变体来验证这一点。 :
module Main where

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

{-# INLINE foldlpInline #-}
foldlpInline :: (b -> a -> b) -> (b -> Bool) -> b -> [a] -> b
foldlpInline f p = go where
go b [] = b
go b (x : xs)
| p b = go (f b x) xs
| otherwise = b


{-# INLINE foldlp' #-} -- So that the code is not optimized
foldlp' b [] = b
foldlp' b (x : xs)
| (/= 0) b = foldlp' (mult b x) xs
| otherwise = b

mult :: Integer -> Integer -> Integer
mult 0 _ = 0
mult x y = x * y

--main = print $ foldlp mult (/= 0) 1 $ replicate (10 ^ 7) 1
--main = print $ foldlpInline mult (/= 0) 1 $ replicate (10 ^ 7) 1
main = print $ foldlp' 1 $ replicate (10 ^ 7) 1

结果是:

第一种情况(正常的非内联):
./test  0,42s user 0,01s system 96% cpu 0,446 total

第二种情况(内联):
./test  0,83s user 0,02s system 98% cpu 0,862 total

第三种情况(编译器为非内联生成什么)
./test  0,42s user 0,01s system 99% cpu 0,432 total

关于haskell - 为什么添加 INLINE 会减慢我的程序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39991318/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com