【发布时间】:2014-07-07 23:14:34
【问题描述】:
我喜欢 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
【问题讨论】:
-
请确认您在没有附加调试器的情况下以发布模式运行此程序。除此之外,基准似乎是有效的。您可以通过将工作量增加 10 倍来消除一次性成本的影响来改进它。
-
@usr 是的,我在没有调试器的情况下运行它并在发布模式下编译。毫无疑问,性能差异是真实存在的,因为它可以从 IL 代码中推断出来(除非 JIT 优化)。
-
@Arbil 我已在有关内联分析的 F# 语言设计 UserVoice 线程之一上链接到此问题:fslang.uservoice.com/forums/245727-f-language/suggestions/…
-
这里是关于内联优化的另一个讨论:github.com/fsharp/fsharp/issues/162
-
@JackP。谢谢。据我了解,fslang 提案更先进——它是关于算法的启发式分析。这里没有算法上的微妙之处——如果我将二阶函数和它的一阶参数函数都标记为内联,我希望应用程序的结果“一直向下”内联。这对编译器来说是一个明确的指令。我会在 fslang 中提到这一点。
标签: performance f#