内联派生类型类方法

时间:2018-10-02 02:10:51

标签: haskell

Haskell使您可以派生类型类实例,例如:

{-# LANGUAGE DeriveFunctor #-}

data Foo a = MakeFoo a a deriving (Functor)

...但是,有时基准测试表明,如果手动实现typeclass实例并使用INLINE注释type class方法,则性能会提高,如下所示:

data Foo a = MakeFoo a a

instance Functor Foo where
    fmap f (MakeFoo x y) = MakeFoo (f x) (f y)
    {-# INLINE fmap #-}

有没有办法做到两全其美?换句话说,是否有一种方法可以派生类型类实例并用INLINE注释派生的类型类方法?

1 个答案:

答案 0 :(得分:3)

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

  

-fspecialise-aggressively消除了关于哪些功能可以特殊化的限制。任何重载的功能将是   专门使用此标志。这可能会产生很多   附加代码。

     

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

     

结合使用这两个标志将具有几乎相同的效果   除了将以下事实标记为INLINABLE:   INLINABLE定义的展开未得到优化。

(来源:https://wiki.haskell.org/Inlining_and_Specialisation#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])

编译时不带参数:

$ time ./functor 

real    0m4.036s
user    0m3.550s
sys 0m0.485s

编译为-fexpose-all-unfoldings

$ time ./functor

real    0m3.662s
user    0m3.258s
sys 0m0.404s

这是此编译中的.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

编译为-fspecialise-aggressively

$ time ./functor

real    0m3.761s
user    0m3.300s
sys 0m0.460s

同时带有两个标志:

$ time ./functor

real    0m3.665s
user    0m3.213s
sys 0m0.452s

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