举个例子,假设我想在列表上写一个monadic和非monadic地图。我将从monadic开始:
import Control.Monad
import Control.Monad.Identity
mapM' :: (Monad m) => (a -> m b) -> ([a] -> m [b])
mapM' _ [] = return []
mapM' f (x:xs) = liftM2 (:) (f x) (mapM f xs)
现在我想重用代码来编写纯map
(而不是重复代码):
map' :: (a -> b) -> ([a] -> [b])
map' f = runIdentity . mapM' (Identity . f)
有必要将map'
优化为written explicitly like map
is进行优化吗?特别是:
是否有必要写
{-# SPECIALIZE mapM' :: (a -> Identity b) -> ([a] -> Identity [b]) #-}
或GHC是否优化map'
本身(通过完全分解Identity
)?
还需要添加其他(更多pragma)吗?
map'
代码验证已编译的map
的优化程度?答案 0 :(得分:19)
好吧,让我们问编译器本身。
编译模块
module PMap where
import Control.Monad
import Control.Monad.Identity
mapM' :: (Monad m) => (a -> m b) -> ([a] -> m [b])
mapM' _ [] = return []
mapM' f (x:xs) = liftM2 (:) (f x) (mapM f xs)
map' :: (a -> b) -> ([a] -> [b])
map' f = runIdentity . mapM' (Identity . f)
带有ghc -O2 -ddump-simpl -ddump-to-file PMap.hs
的(ghc-7.6.1,7.4.2产生相同的名称除了唯一名称)为map'
生成以下核心
PMap.map'
:: forall a_afB b_afC. (a_afB -> b_afC) -> [a_afB] -> [b_afC]
[GblId,
Arity=2,
Caf=NoCafRefs,
Str=DmdType LS,
Unf=Unf{Src=<vanilla>, TopLvl=True, Arity=2, Value=True,
ConLike=True, WorkFree=True, Expandable=True,
Guidance=IF_ARGS [60 30] 160 40}]
PMap.map' =
\ (@ a_c) (@ b_d) (f_afK :: a_c -> b_d) (eta_B1 :: [a_c]) ->
case eta_B1 of _ {
[] -> GHC.Types.[] @ b_d;
: x_afH xs_afI ->
GHC.Types.:
@ b_d
(f_afK x_afH)
(letrec {
go_ahZ [Occ=LoopBreaker]
:: [a_c] -> Data.Functor.Identity.Identity [b_d]
[LclId, Arity=1, Str=DmdType S]
go_ahZ =
\ (ds_ai0 :: [a_c]) ->
case ds_ai0 of _ {
[] ->
(GHC.Types.[] @ b_d)
`cast` (Sym <(Data.Functor.Identity.NTCo:Identity <[b_d]>)>
:: [b_d] ~# Data.Functor.Identity.Identity [b_d]);
: y_ai5 ys_ai6 ->
(GHC.Types.:
@ b_d
(f_afK y_ai5)
((go_ahZ ys_ai6)
`cast` (<Data.Functor.Identity.NTCo:Identity <[b_d]>>
:: Data.Functor.Identity.Identity [b_d] ~# [b_d])))
`cast` (Sym <(Data.Functor.Identity.NTCo:Identity <[b_d]>)>
:: [b_d] ~# Data.Functor.Identity.Identity [b_d])
}; } in
(go_ahZ xs_afI)
`cast` (<Data.Functor.Identity.NTCo:Identity <[b_d]>>
:: Data.Functor.Identity.Identity [b_d] ~# [b_d]))
}
是的,只有cast
s,没有真正的开销。您得到的本地工作人员go
的行为与map
完全相同。
总结:您只需要-O2
,并且您可以通过查看核心(-ddump-simpl
)来验证代码的优化程度,或者,如果您可以在生产的程序集中阅读它( -ddump-asm
)resp LLVM位代码-ddump-llvm
)。
稍微详细说明可能是件好事。关于
是否有必要写
{-# SPECIALIZE mapM' :: (a -> Identity b) -> ([a] -> Identity [b]) #-}
或GHC是否优化
map'
本身(通过完全分解身份)?
答案是,如果你在定义一般函数的同一个模块中使用特化,那么一般来说你不需要{-# SPECIALISE #-}
编译指示,GHC如果看到任何一个就创建了自己的特化从中受益。在上面的模块中,GHC创建了专门化规则
"SPEC PMap.mapM' [Data.Functor.Identity.Identity]" [ALWAYS]
forall (@ a_abG)
(@ b_abH)
($dMonad_sdL :: GHC.Base.Monad Data.Functor.Identity.Identity).
PMap.mapM' @ Data.Functor.Identity.Identity
@ a_abG
@ b_abH
$dMonad_sdL
= PMap.mapM'_$smapM' @ a_abG @ b_abH
这也有利于定义模块之外mapM'
monad的Identity
的任何使用(如果使用优化进行编译,并且monad在规则触发时被识别为Identity
)。
但是,如果GHC不能很好地理解专业化的类型,它可能看不到任何好处而且没有专门化(我不知道它是否足以告诉它是否会尝试 - 到目前为止我找到了每次我看一个专业化。)
如果你想确定,请查看核心。
如果您需要在不同模块中进行专业化,GHC在编译定义模块时没有理由专门使用该函数,因此在这种情况下需要编译指示。而不是{-# SPECIALISE #-}
pragma要求对一些精选的类型进行专门化,从ghc-7开始,使用{-# INLINABLE #-}
pragma可能更好,以便(略微修改)源代码可以在导入模块中访问,这允许对那里的任何所需类型进行特化。
还需要添加其他(更多pragma)吗?
不同的用途当然可能需要不同的编译指示,但根据经验,{#- INLINABLE #-}
是您最想要的。当然,{-# RULES #-}
可以使编译器无法独立完成魔术。
如何使用明确编写的
map'
代码验证已编译的map
的优化程度?