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 。 我怀疑一个“真正的”计划不会那么微妙。
