当嵌套是可选的时,嵌套f#函数与不嵌套它们有何不同?

时间:2016-01-27 16:21:09

标签: f# inline

当你想要返回一个闭包时,我理解嵌套函数的好处。但是当一个函数从未在它定义的函数之外使用时,是否需要保持嵌套的成本?

考虑:

let private validate number = 
    number > 100

let validateAndPrint (i : int) = 
    printfn "%i Greater than 100: %s" i ((validate i).ToString())

对战:

let validateAndPrint (i : int) = 
    let validate number = 
        number > 100
    printfn "%i Greater than 100: %s" i ((validate i).ToString())

这里的问题通常是当我有一些这些内部函数时,我只需要根据执行路径调用其中一个函数(它通常不会禁止将这些函数保密或嵌套并测试排列)。

validateAndPrint也可以重写为tdd-friendly以接受验证功能:

let validateAndPrint validate i = ...

..但如果validation的签名包含内部或私人类型,则需要考虑将所有内容公开,并且参数列表会爆炸。

任何重要的差异?

1 个答案:

答案 0 :(得分:6)

ffprobe模式下,IL是相同的,并且在此特定示例中都内联了Release的两个版本。

您不能总是依赖内联,例如如果一个函数是"大"然后编译器可以选择不内联它(这对于方法来说是正确的,我不是100%确定这对于F#嵌套函数是正确的。不可能标记它们validate并强制内联, - 需要咨询F# specs,如果这些函数的行为类似于方法,或者总是在Release中内联)。在调试模式的嵌套情况下,还有额外的FSharpFunc分配和inline。如果没有嵌套,它只是一个没有分配的静态调用,这是所有可能实现中最便宜的。

所以一般来说,如果嵌套是真正可选的,那么避免使用它会更安全,因为即使函数没有内联,它也总是被称为静态方法。但是,只有当您每秒调用此代码数百万次时才重要。

IL_0019上没有嵌套的静态调用:

callvirt

嵌套,IL_0018上的分配和IL_0020上的callvirt:

.method public static 
    void validateAndPrint (
        int32 i
    ) cil managed 
{
    // Method begins at RVA 0x2104
    // Code size 51 (0x33)
    .maxstack 5
    .locals init (
        [0] class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<int32, class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<string, class [FSharp.Core]Microsoft.FSharp.Core.Unit>>,
        [1] bool
    )

    IL_0000: ldstr "%i Greater than 100: %s"
    IL_0005: newobj instance void class [FSharp.Core]Microsoft.FSharp.Core.PrintfFormat`5<class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<int32, class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<string, class [FSharp.Core]Microsoft.FSharp.Core.Unit>>, class [mscorlib]System.IO.TextWriter, class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [mscorlib]System.Tuple`2<int32, string>>::.ctor(string)
    IL_000a: call !!0 [FSharp.Core]Microsoft.FSharp.Core.ExtraTopLevelOperators::PrintFormatLine<class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<int32, class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<string, class [FSharp.Core]Microsoft.FSharp.Core.Unit>>>(class [FSharp.Core]Microsoft.FSharp.Core.PrintfFormat`4<!!0, class [mscorlib]System.IO.TextWriter, class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [FSharp.Core]Microsoft.FSharp.Core.Unit>)
    IL_000f: stloc.0
    IL_0010: nop
    IL_0011: ldloc.0
    IL_0012: newobj instance void FSSO.Test/validateAndPrint@8::.ctor(class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<int32, class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<string, class [FSharp.Core]Microsoft.FSharp.Core.Unit>>)
    IL_0017: ldarg.0
    IL_0018: ldarg.0
    IL_0019: call bool FSSO.Test::validate(int32)
    IL_001e: stloc.1
    IL_001f: ldloca.s 1
    IL_0021: constrained. [mscorlib]System.Boolean
    IL_0027: callvirt instance string [mscorlib]System.Object::ToString()
    IL_002c: call !!0 class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<int32, string>::InvokeFast<class [FSharp.Core]Microsoft.FSharp.Core.Unit>(class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<!0, class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<!1, !!0>>, !0, !1)
    IL_0031: pop
    IL_0032: ret
} // end of method Test::validateAndPrint

发布模式,IL_0020的内联比较:

.method public static 
    void validateAndPrint (
        int32 i
    ) cil managed 
{
    // Method begins at RVA 0x2050
    // Code size 58 (0x3a)
    .maxstack 6
    .locals init (
        [0] class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<int32, bool> validate,
        [1] class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<int32, class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<string, class [FSharp.Core]Microsoft.FSharp.Core.Unit>>,
        [2] bool
    )

    IL_0000: newobj instance void FSSO.Test2/validate@13::.ctor()
    IL_0005: stloc.0
    IL_0006: ldstr "%i Greater than 100: %s"
    IL_000b: newobj instance void class [FSharp.Core]Microsoft.FSharp.Core.PrintfFormat`5<class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<int32, class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<string, class [FSharp.Core]Microsoft.FSharp.Core.Unit>>, class [mscorlib]System.IO.TextWriter, class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [mscorlib]System.Tuple`2<int32, string>>::.ctor(string)
    IL_0010: call !!0 [FSharp.Core]Microsoft.FSharp.Core.ExtraTopLevelOperators::PrintFormatLine<class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<int32, class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<string, class [FSharp.Core]Microsoft.FSharp.Core.Unit>>>(class [FSharp.Core]Microsoft.FSharp.Core.PrintfFormat`4<!!0, class [mscorlib]System.IO.TextWriter, class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [FSharp.Core]Microsoft.FSharp.Core.Unit>)
    IL_0015: stloc.1
    IL_0016: nop
    IL_0017: ldloc.1
    IL_0018: newobj instance void FSSO.Test2/'validateAndPrint@14-2'::.ctor(class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<int32, class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<string, class [FSharp.Core]Microsoft.FSharp.Core.Unit>>)
    IL_001d: ldarg.0
    IL_001e: ldloc.0
    IL_001f: ldarg.0
    IL_0020: callvirt instance !1 class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<int32, bool>::Invoke(!0)
    IL_0025: stloc.2
    IL_0026: ldloca.s 2
    IL_0028: constrained. [mscorlib]System.Boolean
    IL_002e: callvirt instance string [mscorlib]System.Object::ToString()
    IL_0033: call !!0 class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<int32, string>::InvokeFast<class [FSharp.Core]Microsoft.FSharp.Core.Unit>(class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<!0, class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<!1, !!0>>, !0, !1)
    IL_0038: pop
    IL_0039: ret
} // end of method Test2::validateAndPrint