You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
We noticed an unnecessary allocation which occurs when calling a (unit -> unit) function obtained from an array. The allocation appears to go away when the action of getting from the array is abstracted into a helper function. The allocation does not occur when invoking a System.Action.
Repro steps
Compile the following code on version 4.1 of the F# compiler. (Would be worth testing on a later version, we can try this later).
let f (arr : (unit -> unit) array) =
for i = 0 to arr.Length - 1 do
arr.[i] ()
Observe the IL using ILSpy or run a profiler on this function:
.method public static
void f (
class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [FSharp.Core]Microsoft.FSharp.Core.Unit>[] arr
) cil managed
{
// Method begins at RVA 0x2050
// Code size 29 (0x1d)
.maxstack 6
.locals init (
[0] int32
)
IL_0000: ldc.i4.0
IL_0001: stloc.0
IL_0002: br.s IL_0016
// loop start (head: IL_0016)
IL_0004: newobj instance void X.Y/f@9::.ctor()
IL_0009: ldarg.0
IL_000a: ldloc.0
IL_000b: ldnull
IL_000c: call !!1 class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [FSharp.Core]Microsoft.FSharp.Core.Unit>[], int32>::InvokeFast<class [FSharp.Core]Microsoft.FSharp.Core.Unit, 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, class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<!!0, !!1>>>, !0, !1, !!0)
IL_0011: pop
IL_0012: ldloc.0
IL_0013: ldc.i4.1
IL_0014: add
IL_0015: stloc.0
IL_0016: ldloc.0
IL_0017: ldarg.0
IL_0018: ldlen
IL_0019: conv.i4
IL_001a: blt.s IL_0004
// end loop
IL_001c: ret
} // end of method Y::f
The point of interest is IL_0004, which is an unnecesary newobj call.
By refactoring the code very slightly, the allocation is avoided:
let g (arr : (unit -> unit) array) =
let get i = arr.[i]
for i = 0 to arr.Length - 1 do
get i ()
Which produces the following IL:
.method assembly static
class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [FSharp.Core]Microsoft.FSharp.Core.Unit> get@12 (
class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [FSharp.Core]Microsoft.FSharp.Core.Unit>[] arr,
int32 i
) cil managed
{
// Method begins at RVA 0x207c
// Code size 8 (0x8)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: ldelem class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [FSharp.Core]Microsoft.FSharp.Core.Unit>
IL_0007: ret
} // end of method Y::get@12
.method public static
void g (
class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [FSharp.Core]Microsoft.FSharp.Core.Unit>[] arr
) cil managed
{
// Method begins at RVA 0x2088
// Code size 29 (0x1d)
.maxstack 4
.locals init (
[0] int32
)
IL_0000: ldc.i4.0
IL_0001: stloc.0
IL_0002: br.s IL_0016
// loop start (head: IL_0016)
IL_0004: ldarg.0
IL_0005: ldloc.0
IL_0006: call class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [FSharp.Core]Microsoft.FSharp.Core.Unit> X.Y::get@12(class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [FSharp.Core]Microsoft.FSharp.Core.Unit>[], int32)
IL_000b: ldnull
IL_000c: callvirt instance !1 class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [FSharp.Core]Microsoft.FSharp.Core.Unit>::Invoke(!0)
IL_0011: pop
IL_0012: ldloc.0
IL_0013: ldc.i4.1
IL_0014: add
IL_0015: stloc.0
IL_0016: ldloc.0
IL_0017: ldarg.0
IL_0018: ldlen
IL_0019: conv.i4
IL_001a: blt.s IL_0004
// end loop
IL_001c: ret
} // end of method Y::g
Expected behavior
The function f does not allocate at all.
Actual behavior
The function f allocates.
Known workarounds
Using g
Related information
Windows 10
.NET Framework 4.6.1
F# Compiler version 10.1.0
The text was updated successfully, but these errors were encountered:
We noticed an unnecessary allocation which occurs when calling a
(unit -> unit)
function obtained from an array. The allocation appears to go away when the action of getting from the array is abstracted into a helper function. The allocation does not occur when invoking a System.Action.Repro steps
Compile the following code on version 4.1 of the F# compiler. (Would be worth testing on a later version, we can try this later).
Observe the IL using ILSpy or run a profiler on this function:
The point of interest is IL_0004, which is an unnecesary newobj call.
By refactoring the code very slightly, the allocation is avoided:
Which produces the following IL:
Expected behavior
The function
f
does not allocate at all.Actual behavior
The function
f
allocates.Known workarounds
Using
g
Related information
The text was updated successfully, but these errors were encountered: