我在做一些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
内置的内置设备是否更灵活?
答案 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
的生成方式存在轻微的性能错误。在这种情况下,您可以尝试使用尾递归来实现循环。