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?注释派生的类型类方法?
尽管您无法像使用动态语言中的类那样“重新打开”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(并且它确实会对性能产生不可忽略的影响)。