One of the things I love about F# is a real inline
keyword. However, while it allows to write first order functions that perform the same as pasted code blocks, things aren't so rosy for higher order functions. Consider
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
The results are 244 ms
for iter
and 61 ms
for manual checks. Let's delve into ILSpy. The relevant function called for the direct call is:
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);
}
}
}
With add
inlined. The relevant function for iter
is
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;
}
}
And we can see the performance penalty comes from one extra level of indirection. As the performance test shows, this indirection is also not removed by the JIT compiler. Is there a reason for why the higher order functions cannot be fully inlined? This is a pain when writing a computational kernel.
My time combinator (though not really relevant here) is
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
USAF sought to retire the early F-22s, currently rated for training use only because they are expensive to maintain and are increasingly mismatched to the combat-coded versions, reducing their value as training platforms. The roughly $1 billion cost to upgrade those jets was not affordable, Air Force officials said.
In spring 2020, officials banned the F-35A from flying within 25 nautical miles of lightning or thunderstorms after finding that a crucial system may not function correctly if hit by a bolt. At issue is the Onboard Inert Gas Generation System, or OBIGGS, which injects nitrogen-enriched air into the jet's fuel tanks.
U.S. Air Force plans to begin retirement of F-22 Raptor fifth generation fighters from service in 2023 have been met with strong opposition from both the House and Senate Armed Services Committees, which have pressed to not only keep the small fleet at full strength but also to invest heavily in upgrades.
The F-22 fleet, which now numbers 182 aircraft, has experienced 32 “Class A” mishaps and 50 “Class B” accidents over the past 21 years. A Class A accident involves a fatality, loss of the aircraft, or more than $2.5 million in damage.
Just to be clear, the F# compiler is inlining every definition that you've marked as inline
. It's just that the current behavior of inlining isn't very useful when using an inlined function as a higher-order argument. check
can only be inlined when given an argument, and so iter runs check
is treated as iter runs (fun i -> check i)
. Then check
gets inlined, resulting in the equivalent of
iter runs (fun i -> if (add i) = 0 then printfn "")
(as you can see in the IL, there is no call to check
in the generated IL, but there is a call to the synthetic f@315-5
body for this lambda, which is equivalent). iter
gets inlined, too.
Having said that, I agree that the current behavior isn't as useful as it could be - the compiler could also inline the body of the lambda into the call site, which would be safe and improve performance.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With