Haskell性能示例

cro*_*eea 12 performance haskell ghc

我的代码中有这些基本类型:

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
Run Code Online (Sandbox Code Playgroud)

(忽略FactFactored暂时).我标杆不同层次的代码,比较的性能foo,tbar.在基准,t = foobar只用于t通过newtype.因此,它们的运行时应基本相同,但标准报告foo需要9.2ns,t大约是17.45ns的两倍,并且bar需要高达268.1ns.

我已尝试添加INLINABLE甚至是一个SPECIALIZEpragma,但他们没有帮助.我想相信GHC有一些神奇的语法/优化/等可以一致地应用于解决这些类型的性能问题.例如,我已经看到过以无点样式编写代码具有显着性能影响的情况.

完整的代码可以在这里找到.我保证这不会令人生畏.这些模块是:

  • 富:定义Foo,fooT
  • Bar:定义Barbar
  • FooBench:定义了一个基准 foo
  • TBench:定义了一个基准 t
  • BarBench:定义基准 bar
  • 主要:运行三个基准
  • 因素:定义FactFactored使用单身人士

大多数模块很小; 我在单独的文件中定义了三个基准,以便我可以检查它们的核心.我为这三个*Bench模块生成了核心,并尽可能地将它们对齐.它们每个只有~250行,前两行是相同的,直到重命名.问题是我不知道该怎么做最后50行.在(核)扩散的FooBenchVS TBench在这里,参与比较TBenchVS BarBench在这里,并为DIFF FooBenchVS BarBench在这里.

我只有几个问题:

  1. 从较高的层面来看,核心文件之间的本质区别是什么?我正在寻找类似" 在这里你可以看到GHC没有内联电话x." 我应该寻找什么?

  2. 如何才能使这三个基准测试在约9.2ns内完成?GHC优化?INLINE/ INLINABLEpragmas?SPECIALIZE我错过了pragma?(您不能专门用于F128::Factored;在我的真实库中,此值可能会在运行时生效.)限制/延迟内联到特定阶段?

虽然我正在寻找一个实际的解决方案来快速完成基准测试,但是这个例子的技巧可能无法扩展到我的真实库.因此,我也在寻找特定技术应该起作用的"高级"解释.

cch*_*ers 6

首先,看看bar:

bar :: (Fact m, T t) => Bar t m -> Bar t m
bar (Bar v) = Bar $ t v
Run Code Online (Sandbox Code Playgroud)

我们可以在不需要参数的情况下编写这个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
Run Code Online (Sandbox Code Playgroud)

这(我们希望)得到的bar表现与之相同t.(实际上核心TBenchBarBench完全相同,不包括类型签名).

我不完全确定为什么,但使用INLINE而不是INLINEABLE制作tbar执行相同foo.我不是专家,但通常最好使用INLINE您确定要内联的小功能.

也就是说,我认为其中一些问题来自于基准如何停止ghc作弊.例如,bench "Bar" $ nf (GHC.Magic.inline bar) x在原始代码上bar执行的操作与执行相同foo.我怀疑一个"真正的"程序不会那么微妙.