F#Array.reduce的性能

时间:2016-07-23 12:49:24

标签: performance f#

我在做一些F#实验时注意到,如果为Array编写我自己的reduce函数,它的执行效果比内置的reduce要好得多。例如:

type Array with
    static member inline fastReduce f (values : 'T[]) =
        let mutable result = Unchecked.defaultof<'T>
        for i in 0 .. values.Length-1 do
            result  <- f result values.[i]
        result

这似乎与内置Array.reduce的行为相同,但对于简单的f

,速度提高约2倍

内置的内置设备是否更灵活?

1 个答案:

答案 0 :(得分:7)

通过查看生成的IL代码,您可以更轻松地了解正在发生的事情。

使用内置Array.reduce

let reducer (vs : int []) : int = Array.reduce (+) vs

提供以下等效C#(使用ILSpy从IL代码反向设计)

public static int reducer(int[] vs)
{
  return ArrayModule.Reduce<int>(new Program.BuiltIn.reducer@31(), vs);
}

Array.reduce看起来像这样:

public static T Reduce<T>(FSharpFunc<T, FSharpFunc<T, T>> reduction, T[] array)
{
  if (array == null)
  {
    throw new ArgumentNullException("array");
  }
  int num = array.Length;
  if (num == 0)
  {
    throw new ArgumentException(LanguagePrimitives.ErrorStrings.InputArrayEmptyString, "array");
  }
  OptimizedClosures.FSharpFunc<T, T, T> fSharpFunc = OptimizedClosures.FSharpFunc<T, T, T>.Adapt(reduction);
  T t = array[0];
  int num2 = 1;
  int num3 = num - 1;
  if (num3 >= num2)
  {
    do
    {
      t = fSharpFunc.Invoke(t, array[num2]);
      num2++;
    }
    while (num2 != num3 + 1);
  }
  return t;
}

请注意,它调用reducer函数f是一个虚拟调用,通常是JIT:er难以内联。

与您的fastReduce功能进行比较:

let reducer (vs : int []) : int = Array.fastReduce (+) vs

反向工程C#代码:

public static int reducer(int[] vs)
{
  int num = 0;
  for (int i = 0; i < vs.Length; i++)
  {
    num += vs[i];
  }
  return num;
}

随着虚拟呼叫的消失,效率会提高很多。在这种情况下,似乎F#内联了fastReduce以及(+)的代码。

F#中存在某种截止,因为更复杂的缩减器功能不会被内联。我不确定具体细节。

希望这有帮助

旁注; Unchecked.defaultOf为.NET中的类类型返回null值,例如string。我更喜欢LanguagePrimitives.GenericZero

PS。真正的性能饥饿的一个常见技巧是循环到0。在F#中,for-expressions不起作用,因为for-expressions的生成方式存在轻微的性能错误。在这种情况下,您可以尝试使用尾递归来实现循环。