Haskell性能示例
我的代码中有这些基本类型:
newtype Foo (m :: Factored) = Foo Int64 deriving (NFData)
foo :: forall m . (Fact m) => Foo m -> Foo m
class T t where t :: (Fact m ) => t m -> t m
instance T Foo where t = foo
newtype Bar t (m :: Factored) = Bar (t m) deriving (NFData)
bar :: (Fact m, T t) => Bar t m -> Bar t m
bar (Bar v) = Bar $ t v
( Factored
忽略Fact
和Factored
)。 我在不同层次上对代码进行基准测试,比较foo
, t
和bar
的性能。 在基准, t = foo
,和bar
只用于t
通过newtype
。 因此,它们的运行时间应该基本相同,但标准报告foo
需要9.2ns, t
需要17.45ns的两倍,而bar
需要高达268.1ns。
我已经尝试添加INLINABLE
,甚至是SPECIALIZE
编译指示,但他们没有帮助。 我想相信GHC有一些神奇的语法/优化/等,可以一致地应用于解决这些类型的性能问题。 例如,我曾经看到过使用无点式编写代码会对性能产生巨大影响的情况。
完整的代码可以在这里找到。 我保证它不是吓人的。 这些模块是:
Foo
, foo
和T
Bar
和bar
foo
定义一个基准 t
定义一个基准 bar
定义基准 Fact
和Factored
大多数模块很小, 我在单独的文件中定义了三个基准,以便我可以检查它们的核心。 我生成了三个*Bench
模块的核心,并将它们*Bench
地对齐。 他们只有〜250行,第一〜200行是相同的,直到重命名。 问题是,我不知道最后50行左右应该怎么做。 FooBench
vs TBench
核心差异在这里, TBench
vs BarBench
的差异在这里,而FooBench
vs BarBench
的差异在这里。
我只有几个问题:
在高层次上,核心文件之间的本质区别是什么? 我正在寻找类似“在这里你可以看到GHC没有内联对x
的调用。” 我应该寻找什么?
可以做些什么来使三个基准都在9.2ns左右运行? GHC优化? INLINE
/ INLINABLE
? 我错过了SPECIALIZE
pragmas? (你不能专门用于F128::Factored
;在我的真实库中,这个值可能在运行时被指定。)限制/延迟内联到特定阶段?
尽管我正在寻找一种实际的解决方案来快速制定基准测试,但可能这个示例的技巧不会扩展到我的真实库。 因此,我还在寻找为什么应用特定技术的“高层次”解释。
首先bar
:
bar :: (Fact m, T t) => Bar t m -> Bar t m
bar (Bar v) = Bar $ t v
我们可以在不需要使用coerce
参数的情况下编写它:
bar :: (Fact m, T t) => Bar t m -> Bar t m
bar = (coerce :: (t m -> t m) -> Bar t m -> Bar t m) t
这(正如我们希望的那样)得到的bar
表现与t
相同。 (事实上, TBench
和BarBench
的核心是完全一样的,不包括类型签名)。
我不完全确定为什么,但使用INLINE
而不是INLINEABLE
使t
和bar
与foo
执行相同。 我不是专家,但通常使用INLINE
作为您确定要内联的小功能通常会更好。
这就是说,我认为其中的一些问题是从基准测试的标准来制止ghc
作弊。 例如,写bench "Bar" $ nf (GHC.Magic.inline bar) x
你原来的代码有bar
表演一样foo
。 我怀疑一个“真正的”计划不会那么微妙。