我喜欢F#的一个问题是真正的inline
关键字。然而,虽然它允许编写与粘贴的代码块执行相同的一阶函数,但对于更高阶函数来说,事情并不乐观。考虑
let inline add i = i+1
let inline check i = if (add i) = 0 then printfn ""
let inline iter runs f = for i = 0 to runs-1 do f i
let runs = 100000000
time(fun()->iter runs check) 1
time(fun()->for i = 0 to runs-1 do check i) 1
244 ms
和iter
的结果为61 ms
,用于手动检查。让我们深入研究ILSpy。要求直接呼叫的相关功能是:
internal static void func@22-12(Microsoft.FSharp.Core.Unit unitVar0)
{
for (int i = 0; i < 100000000; i++)
{
if (i + 1 == 0)
{
Microsoft.FSharp.Core.PrintfFormat<Microsoft.FSharp.Core.Unit, System.IO.TextWriter, Microsoft.FSharp.Core.Unit, Microsoft.FSharp.Core.Unit> format = new Microsoft.FSharp.Core.PrintfFormat<Microsoft.FSharp.Core.Unit, System.IO.TextWriter, Microsoft.FSharp.Core.Unit, Microsoft.FSharp.Core.Unit, Microsoft.FSharp.Core.Unit>("");
Microsoft.FSharp.Core.PrintfModule.PrintFormatLineToTextWriter<Microsoft.FSharp.Core.Unit>(System.Console.Out, format);
}
}
}
内联add
。 iter
的相关功能是
internal static void func@22-11(Microsoft.FSharp.Core.Unit unitVar0)
{
for (int i = 0; i < 100000000; i++)
{
Tests.FunctionInlining.f@315-5(i);
}
}
internal static void f@315-5(int i)
{
if (i + 1 == 0)
{
Microsoft.FSharp.Core.PrintfFormat<Microsoft.FSharp.Core.Unit, System.IO.TextWriter, Microsoft.FSharp.Core.Unit, Microsoft.FSharp.Core.Unit> format = new Microsoft.FSharp.Core.PrintfFormat<Microsoft.FSharp.Core.Unit, System.IO.TextWriter, Microsoft.FSharp.Core.Unit, Microsoft.FSharp.Core.Unit, Microsoft.FSharp.Core.Unit>("");
Microsoft.FSharp.Core.PrintfModule.PrintFormatLineToTextWriter<Microsoft.FSharp.Core.Unit>(System.Console.Out, format);
return;
}
}
我们可以看到性能损失来自一个额外的间接层。正如性能测试所示,JIT编译器也不会消除这种间接性。有没有理由为什么高阶函数不能完全内联?编写计算内核时,这是一种痛苦。
我的时间组合器(虽然这里不太相关)是
let inline time func n =
func() |> ignore
GC.Collect()
GC.WaitForPendingFinalizers()
let stopwatch = Stopwatch.StartNew()
for i = 0 to n-1 do func() |> ignore
stopwatch.Stop()
printfn "Took %A ms" stopwatch.Elapsed.TotalMilliseconds
答案 0 :(得分:6)
为了清楚起见,F#编译器正在内联您标记为inline
的每个定义。只是当使用内联函数作为高阶参数时,内联的当前行为不是很有用。 check
只能在给出参数时进行内联,因此iter runs check
被视为iter runs (fun i -> check i)
。然后check
被内联,导致相当于
iter runs (fun i -> if (add i) = 0 then printfn "")
(正如你在IL中看到的那样,在生成的IL中没有调用check
,但是对这个lambda有一个合成f@315-5
体的调用,这是等价的)。 iter
也会被内联。
话虽如此,我同意当前的行为并没有那么有用 - 编译器也可以将lambda的主体内联到调用站点,这样可以安全并提高性能。