为什么F#inline会导致11倍的性能提升

isa*_*h_p 19 .net performance f# struct inline

我正在研究一些重的cpu绑定问题.当我使用inline关键字时,我看到了很大的性能提升.我从标准.net库创建一个字典,传入自定义密钥Comparer,请参阅下面的代码和时序结果

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

在Eq_cmp上使用内联关键字

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

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

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

pad*_*pad 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;
}
Run Code Online (Sandbox Code Playgroud)

通用等式的效率远低于专用版本.

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

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

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

let rec GenericEqualityIntrinsic (x : 'T) (y : 'T) : bool = 
    fsEqualityComparer.Equals((box x), (box y))
Run Code Online (Sandbox Code Playgroud)

它对参数进行装箱,这解释了第一个例子中的大量垃圾.当GC过于频繁地发挥作用时,它会大大减慢计算速度.第二个例子(using inline)在Pairstruct 时几乎不产生垃圾.

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

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


Jon*_*rop 16

输入专业化

没有inline,你使用的是非常低效的泛型比较.使用时inline,删除通用性并int直接使用比较.

  • @ user125 OCaml确实处理得非常糟糕.默认情况下,每个需要存储至少1个单词的值都会被装箱(尽管unbox浮点数组有一个特殊情况但会在所有数组上产生运行时类型测试).每个泛型函数都通过运行时调度执行多态,这很慢.内联最好是幼稚(小叶函数内联)但也受到函子边界的阻碍.所以OCaml的Hashtbl.t是一个(堆分配的)(标记的)键和值列表的数组.自定义比较和散列意味着仿函数,这意味着内联. (3认同)
  • 最后,您的程序代表了OCaml中的一代垃圾收集器的病态行为.总的来说,我认为F#的速度比OCaml快10倍. (2认同)
  • FWIW,在这台机器上(4x 3.4GHz Intel Core i7-3770)填充一个空的哈希表,10,000,000 int→int绑定在F#中比OCaml快12倍,而浮点→浮点绑定在F#中比OCaml快23倍. (2认同)