内联派生类型类方法

Gab*_*lez 20 haskell

Haskell允许您派生类型类实例,例如:

{-# LANGUAGE DeriveFunctor #-}

data Foo a = MakeFoo a a deriving (Functor)
Run Code Online (Sandbox Code Playgroud)

...但有时基准测试表明,如果您手动实现类型类实例并使用以下方法注释类型类方法,性能会提高INLINE:

data Foo a = MakeFoo a a

instance Functor Foo where
    fmap f (MakeFoo x y) = MakeFoo (f x) (f y)
    {-# INLINE fmap #-}
Run Code Online (Sandbox Code Playgroud)

有没有办法让两全其美?换句话说,有没有办法派生类型类实例,并使用INLINE?注释派生的类型类方法?

Aea*_*nus 6

尽管您无法像使用动态语言中的类那样“重新打开”Haskell 中的实例,但有一些方法可以通过将某些标志传递给 GHC 来确保尽可能积极地内联函数。

-fspecialise-aggressively删除了有关哪些函数可以专门化的限制。任何重载函数都将使用此标志进行专门化。这可能会创建大量额外的代码。

-fexpose-all-unfoldings将包括接口文件中所有函数的(优化)展开,以便它们可以跨模块内联和专门化。

结合使用这两个标志将具有与标记每个定义几乎相同的效果,除了定义的展开未优化INLINABLE这一事实之外。INLINABLE

(来源:https ://wiki.haskell.org/Inlined_and_Specialization#Which_flags_can_I_use_to_control_the_simplifier_and_inliner.3F )

这些选项将允许 GHC 编译器内联fmap. -fexpose-all-unfoldings特别是,该选项允许编译器将 的内部结构公开Data.Functor给程序的其余部分以用于内联目的(并且它似乎提供了最大的性能优势)。这是我整理的一个快速而愚蠢的基准:

functor.hs包含这段代码:

{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE Strict #-}

data Foo a = MakeFoo a a deriving (Functor)

one_fmap foo = fmap (+1) foo

main = sequence (fmap (\n -> return $ one_fmap $ MakeFoo n n) [1..10000000])
Run Code Online (Sandbox Code Playgroud)

无参数编译:

$ time ./functor 

real    0m4.036s
user    0m3.550s
sys 0m0.485s
Run Code Online (Sandbox Code Playgroud)

编译-fexpose-all-unfoldings

$ time ./functor

real    0m3.662s
user    0m3.258s
sys 0m0.404s
Run Code Online (Sandbox Code Playgroud)

这是.prof此编译的文件,以显示对的调用fmap确实被内联:

    Sun Oct  7 00:06 2018 Time and Allocation Profiling Report  (Final)

       functor +RTS -p -RTS

    total time  =        1.95 secs   (1952 ticks @ 1000 us, 1 processor)
    total alloc = 4,240,039,224 bytes  (excludes profiling overheads)

COST CENTRE MODULE SRC              %time %alloc

CAF         Main   <entire-module>  100.0  100.0


                                                                     individual      inherited
COST CENTRE MODULE                SRC             no.     entries  %time %alloc   %time %alloc

MAIN        MAIN                  <built-in>       44          0    0.0    0.0   100.0  100.0
 CAF        Main                  <entire-module>  87          0  100.0  100.0   100.0  100.0
 CAF        GHC.IO.Handle.FD      <entire-module>  84          0    0.0    0.0     0.0    0.0
 CAF        GHC.IO.Encoding       <entire-module>  77          0    0.0    0.0     0.0    0.0
 CAF        GHC.Conc.Signal       <entire-module>  71          0    0.0    0.0     0.0    0.0
 CAF        GHC.IO.Encoding.Iconv <entire-module>  58          0    0.0    0.0     0.0    0.0
Run Code Online (Sandbox Code Playgroud)

编译-fspecialise-aggressively

$ time ./functor

real    0m3.761s
user    0m3.300s
sys 0m0.460s
Run Code Online (Sandbox Code Playgroud)

使用两个标志编译:

$ time ./functor

real    0m3.665s
user    0m3.213s
sys 0m0.452s
Run Code Online (Sandbox Code Playgroud)

这些小基准测试绝不代表实际代码中的性能(或文件大小),但它确实表明您可以强制 GHC 编译器内联fmap(并且它确实会对性能产生不可忽略的影响)。

  • 这对于我的用例来说已经足够好了,因为如果我想限制它的范围,我可以将类型及其实例拆分到一个单独的 Haskell 模块中,然后添加 `{-# OPTIONS_GHC -fexpose-all-unfoldings #-}`或“{-# OPTIONS_GHC -fspecialise-aggressively #-}”到模块顶部 (2认同)