F#自动泛化和性能

Mat*_*ias 7 optimization f# automatic-generalization

我最近遇到了一个意想不到的代码优化,并想检查我对我观察的内容的解释是否正确.以下是一个简化的情况示例:

let demo =
   let swap fst snd i =
       if i = fst then snd else
       if i = snd then fst else
       i
   [ for i in 1 .. 10000 -> swap 1 i i ]

let demo2 =
   let swap (fst: int) snd i =
       if i = fst then snd else
       if i = snd then fst else
       i
   [ for i in 1 .. 10000 -> swap 1 i i ] 
Run Code Online (Sandbox Code Playgroud)

2个代码块之间的唯一区别是在第二种情况下,我明确地将swap的参数声明为整数.然而,当我用#time在fsi中运行2个片段时,我得到:

情况1实时:00:00:00.011,CPU:00:00:00.000,GC gen0:0,gen1:0,gen2:0
情况2实时:00:00:00.004,CPU:00:00:00.015,GC gen0 :0,gen1:0,gen2:0

即第二个片段比第一个片段快3倍.这里的绝对性能差异显然不是问题,但如果我使用交换功能很多,它会堆积起来.

我的假设是性能损失的原因是在第一种情况下,swap是通用的并且"需要相等",并检查int是否支持它,而第二种情况不需要检查任何东西.这是发生这种情况的原因,还是我错过了其他的东西?更一般地说,我应该考虑自动泛化是一把双刃剑,也就是说,一个可能会对性能产生意想不到影响的强大功能?

Tom*_*cek 11

我认为这通常与问题中的情况相同为什么这个F#代码这么慢.在该问题中,性能问题是由约束要求引起的comparison,在您的情况下,它是由equality约束引起的.

在这两种情况下,编译的通用代码必须使用接口(和装箱),而专门的编译代码可以直接使用IL指令进行比较或整数或浮点数的相等.

避免性能问题的两种方法是:

  • 擅长使用的代码intfloat像你一样
  • 将函数标记为inline以便编译器自动对其进行专门化

对于较小的函数,第二种方法更好,因为它不会生成太多代码,您仍然可以以通用方式编写函数.如果你只使用单一类型的函数(按设计),那么使用第一种方法可能是合适的.