Optimization 如何正确使用GHC';什么是专门的布拉格语?(示例:使用标识从一元函数专门化纯函数。)

Optimization 如何正确使用GHC';什么是专门的布拉格语?(示例:使用标识从一元函数专门化纯函数。),optimization,haskell,ghc,explicit-specialization,Optimization,Haskell,Ghc,Explicit Specialization,例如,假设我想在列表上写一个一元和非一元映射。我将从一元开始: 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) 现在我想重用代码来编写纯映射(而不是重复代码): 有什么必要使映射

例如,假设我想在列表上写一个一元和非一元映射。我将从一元开始:

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)
现在我想重用代码来编写纯
映射
(而不是重复代码):

有什么必要使
映射“
尽可能优化?
特别是:

  • 有必要写吗

    {-# SPECIALIZE mapM' :: (a -> Identity b) -> ([a] -> Identity [b]) #-}
    
    {-# SPECIALIZE mapM' :: (a -> Identity b) -> ([a] -> Identity [b]) #-}
    
    或者GHC是否优化了
    map'
    自身(通过完全分解
    标识)

  • 还需要添加其他内容(更多布拉格)

  • 如何验证编译的
    map'
    相对于显式编写的
    map
    代码的优化程度

  • 好吧,让我们问问编译器本身

    编译模块

    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 siml-ddump到文件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]))
        }
    
    或者GHC是否优化了
    map'
    自身(通过完全分解身份)

    答案是,如果您在定义一般功能的同一模块中使用专门化,那么通常您不需要使用
    {-#specialize#-}
    pragma,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
    
    这也有利于在定义模块之外的
    Identity
    monad处使用
    mapM'
    (如果使用优化编译,monad会及时识别为
    Identity
    ,以便触发规则)

    然而,如果GHC不太了解专业化的类型,它可能看不到任何好处,也不专业化(我不太清楚它是否会尝试——到目前为止,我每次看都发现了专业化)

    如果你想确定,看看核心

    如果您需要在不同模块中进行专门化,GHC在编译定义模块时没有理由专门化函数,因此在这种情况下,需要使用pragma。与要求对一些手工挑选的类型进行专门化的
    {-#specialize#-}
    杂注不同,使用
    {-#inlineable#-}
    杂注可能更好,这样(稍加修改的)源代码就可以在导入模块中访问,从而允许对任何需要的类型进行专门化

    还需要添加其他内容(更多布拉格)

    当然,不同的使用可能需要不同的pragma,但根据经验,
    {{iminable}
    是您最想要的。当然,
    {-#规则#-}
    可以发挥编译器自身无法发挥的魔力

    如何验证编译的
    map'
    相对于显式编写的
    map
    代码的优化程度

    • 查看生成的core、asm或llvm位代码,以您最了解的为准(core相对容易)
    • 如果您从核心不确定,并且需要知道,请根据手工编写的专业知识对生成的代码进行基准测试。最终,除非您在某个阶段(core/cmm/asm/llvm)获得相同的中间结果,否则基准测试是唯一确定的方法

    感谢您如此详尽的回答!
    "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