Haskell 内联派生类型类方法
Haskell允许您派生类型类实例,例如:Haskell 内联派生类型类方法,haskell,Haskell,Haskell允许您派生类型类实例,例如: {-# LANGUAGE DeriveFunctor #-} data Foo a = MakeFoo a a deriving (Functor) 。。。但有时基准测试表明,如果手动实现typeclass实例并使用内联注释type类方法,性能会得到提高,如下所示: data Foo a = MakeFoo a a instance Functor Foo where fmap f (MakeFoo x y) = MakeFoo (f x
{-# LANGUAGE DeriveFunctor #-}
data Foo a = MakeFoo a a deriving (Functor)
。。。但有时基准测试表明,如果手动实现typeclass实例并使用内联
注释type类方法,性能会得到提高,如下所示:
data Foo a = MakeFoo a a
instance Functor Foo where
fmap f (MakeFoo x y) = MakeFoo (f x) (f y)
{-# INLINE fmap #-}
有什么办法可以两全其美?换句话说,是否有一种方法可以派生typeclass实例,并使用内联注释派生的typeclass方法?尽管不能像使用动态语言中的类那样在Haskell中“重新打开”实例,有一些方法可以通过向GHC传递某些标志来确保函数尽可能地进行积极的内联
-fsspecialise积极地
消除了关于哪些函数是专门化的限制。任何重载函数都将
专门研究这面旗帜。这可能会产生大量的错误
附加代码
-fexpose所有展开
将包括接口文件中所有函数的(优化)展开,以便它们可以内联和
跨模块专业化
同时使用这两个标志将产生几乎相同的效果
将每个定义标记为可内联
,除了
内置定义的展开未得到优化
(来源:)
这些选项将允许GHC编译器内联fmap
。尤其是-feexpose all unfolings
选项,允许编译器将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所有展开进行编译
:
$ 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
使用两个标志编译:
$ time ./functor
real 0m3.665s
user 0m3.213s
sys 0m0.452s
这些小基准绝不代表实际代码中的性能(或文件大小),但它明确表明您可以强制GHC编译器内联fmap
(并且它确实可以对性能产生不可忽略的影响)。事实上,我想我有一个。这对于我的用例来说已经足够好了,因为如果我想限制它的范围,我可以将类型及其实例拆分成一个单独的Haskell模块,然后添加{-#OPTIONS\u GHC-feexpose all unfolings}
或{-#OPTIONS\u GHC-fspecialise}
至屏幕顶部module@GabrielGonzalez那些注释对我来说是新闻。谢谢你!另外,请注意:您仍然可以标记大型函数NOINLINE
,以确保它们不会内联并破坏文件大小。
$ time ./functor
real 0m3.665s
user 0m3.213s
sys 0m0.452s