19

我正在处理一些繁重的 cpu 绑定问题。当我使用inline关键字时,我看到了很大的性能改进。我从标准 .net 库中创建了一个字典,传入了一个自定义键比较器,请参见下面的代码和计时结果

https://gist.github.com/4409734

在 Eq_cmp 上没有内联关键字

> perf_run 10000000 ;;
Real: 00:00:11.039, CPU: 00:00:11.029, GC gen0: 771, gen1: 3, gen2: 1
val it : unit = ()

在 Eq_cmp 上使用 inline 关键字

perf_run 10000000 ;;
Real: 00:00:01.319, CPU: 00:00:01.388, GC gen0: 1, gen1: 1, gen2: 1
val it : unit = ()
> 

我还注意到 Gen 0 GC 的数量与内联代码和非内联代码的巨大差异。

有人可以解释为什么会有如此巨大的差异吗?

4

2 回答 2

18

inline添加关键字后,我可以通过 3 倍的性能提升来重现我的机器上的行为。

在ILSpy下并排反编译两个版本会得到几乎相同的 C# 代码。显着的区别在于两个相等测试:

// Version without inline
bool IEqualityComparer<Program.Pair<a>>.System-Collections-Generic-IEqualityComparer(Program.Pair<a> x, Program.Pair<a> y)
{
    a v@ = x.v@;
    a v@2 = y.v@;
    if (LanguagePrimitives.HashCompare.GenericEqualityIntrinsic<a>(v@, v@2))
    {
        a w@ = x.w@;
        a w@2 = y.w@;
        return LanguagePrimitives.HashCompare.GenericEqualityIntrinsic<a>(w@, w@2);
    }
    return false;
}

// Version with inline
bool IEqualityComparer<Program.Pair<int>>.System-Collections-Generic-IEqualityComparer(Program.Pair<int> x, Program.Pair<int> y)
{
    int v@ = x.v@;
    int v@2 = y.v@;
    if (v@ == v@2)
    {
        int w@ = x.w@;
        int w@2 = y.w@;
        return w@ == w@2;
    }
    return false;
}

泛型相等的效率远低于专门的版本。

我还注意到 Gen 0 GC 的数量与内联代码和非内联代码的巨大差异。

有人可以解释为什么会有如此巨大的差异吗?

看一下F# 源代码GenericEqualityIntrinsic中的函数:

let rec GenericEqualityIntrinsic (x : 'T) (y : 'T) : bool = 
    fsEqualityComparer.Equals((box x), (box y))

它对参数进行装箱,这解释了您的第一个示例中的大量垃圾。当 GC 过于频繁地发挥作用时,它会显着减慢计算速度。第二个示例(使用)在structinline时几乎不会产生垃圾。Pair

也就是说,inline在调用站点使用专用版本时,这是关键字的预期行为。我的建议是始终尝试在相同的基准上优化和衡量您的代码。

您可能对一个非常相似的线程感兴趣为什么这个 F# 代码这么慢?.

于 2012-12-29T23:49:44.417 回答
16

类型专业化

没有inline,您使用的是非常低效的通用比较。使用inline,删除通用性并int直接使用比较。

于 2012-12-31T17:38:58.787 回答