optimization - 如何正确使用GHC的SPECIALIZE pragma? (例如 : specializing pure function from monadic ones using Identity. )

标签 optimization haskell ghc explicit-specialization

举个例子,假设我想在列表上编写一个单子(monad)和非单子(monad)映射。我将从一元的开始:

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 一样优化,需要什么? ?特别是:

  1. 有必要写

    {-# SPECIALIZE mapM' :: (a -> Identity b) -> ([a] -> Identity [b]) #-}
    

    或者 GHC 是否优化了 map' 本身(通过完全排除 Identity)?

  2. 还需要添加其他内容(更多编译指示)吗?

  3. 如何验证已编译的 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-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,没有真正的开销。你得到一个本地 worker go其作用与 map 完全相同确实如此。

总结:你只需要-O2 ,并且您可以通过查看核心 ( -ddump-simpl ) 来验证代码的优化程度,或者如果您可以阅读它,则可以查看生成的程序集 ( -ddump-asm ) 或 LLVM 位代码 -ddump-llvm 来验证代码的优化程度。 )。

详细说明一下可能会更好。关于

Is it necessary to write

{-# SPECIALIZE mapM' :: (a -> Identity b) -> ([a] -> Identity [b]) #-}

or does GHC optimize map' itself (by factoring out Identity completely)?

答案是,如果您在定义通用函数的同一模块中使用特化,那么通常您不需要 {-# SPECIALISE #-} 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

这也有利于 mapM' 的任何使用在Identity定义模块外部的 monad(如果使用优化进行编译,并且 monad 会及时被识别为 Identity 以触发规则)。

但是,如果 GHC 不能很好地理解专门化的类型,它可能看不到任何好处并且不会专门化(我不太了解它是否会尝试 - 到目前为止我发现每次我看时都会有一个专业)。

如果你想确定的话,看看核心。

如果您需要在不同的模块中进行专门化,GHC 在编译定义模块时没有理由专门化该函数,因此在这种情况下,需要使用编译指示。而不是{-# SPECIALISE #-} pragma 要求对一些精心挑选的类型进行专门化,从 ghc-7 开始,使用 {-# INLINABLE #-} 可能会更好。 pragma,以便在导入模块中可以访问(稍作修改的)源代码,从而允许对那里的任何所需类型进行专门化。

Anything else (more pragmas) need to be added?

不同的用途当然可能需要不同的编译指示,但根据经验,{#- INLINABLE #-}是你最想要的。当然还有{-# RULES #-}可以实现编译器自身无法实现的魔法。

How can I verify how well the compiled map' is optimized wrt the explicitly written code for map?

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

关于optimization - 如何正确使用GHC的SPECIALIZE pragma? (例如 : specializing pure function from monadic ones using Identity. ),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12992481/

相关文章:

java - 性能方面功能的优化实现

haskell - 在 Haskell 中检查 3 个列表中的公共(public)整数的最有效方法

haskell - Monad和单线程有什么关系?

MySql 部分求和

mysql - 如何优化我的事件反馈循环 (Rails)

optimization - 子查询优化实例谈

Haskell inline-c-cpp调用Haskell函数

haskell - cabal repl 对库与可执行文件的不同行为

haskell - "Generalized arrows"和 proc 符号?

haskell - 由于类型错误,无法实现 Foldable 实例