Upcoming F# struct tuples: are they always faster?
Don Syme has been working on struct tuples for F# language. Let's see if they are more performant than "old" (heap allocated) tuples in simple scenario: returning tuple from function. The code is very simple:
Decompiled code in Release configuration:
Everything we need to change to switch to struct tuples, is adding "struct" keyword in front of constructor and pattern matching:
Decompiled code in Release configuration:
I don't know about you, but I was surprised with those results. The performance roughly the same. GC is not a bottleneck as no objects were promoted to generation 1.
Conclusions:
- Using struct tuples as a faster or "GC-friendly" alternative to return multiple values from functions does not make sense.
- Building in release mode erases away heap allocated tuples, but not struct tuples.
- Building in release mode inlines the "foo" function, which makes the code 10x faster.
- You can fearlessly allocate tens of millions of short-living object per second, performance will be great.
Comments
[CompilationMapping(SourceConstructFlags.Module)]
public static class Program
{
[EntryPoint]
public static int main(string[] _arg1)
{
Stopwatch stopwatch = Stopwatch.StartNew();
for (int index = 1; index < 100000001; ++index)
Math.Sin((double) index / 1000.0);
stopwatch.Stop();
PrintfModule.PrintFormatLineToTextWriter>(Console.Out, (PrintfFormat, TextWriter, Unit, Unit>) new PrintfFormat, TextWriter, Unit, Unit, TimeSpan>("Run: %O")).Invoke(stopwatch.Elapsed);
stopwatch.Restart();
GC.Collect(2);
stopwatch.Stop();
PrintfModule.PrintFormatLineToTextWriter>(Console.Out, (PrintfFormat, TextWriter, Unit, Unit>) new PrintfFormat, TextWriter, Unit, Unit, TimeSpan>("GC.Collect: %O")).Invoke(stopwatch.Elapsed);
Console.ReadKey();
return 0;
}
}
I compiled in Release configuration the variant which uses struct tuples and they _are not eraised_:
[EntryPoint]
public static int main(string[] _arg1)
{
Stopwatch sw = Stopwatch.StartNew();
for (int i = 1; i < 100000001; i++)
{
double num = (double)i / 1000.0;
StructTuple structTuple = new StructTuple(num, Math.Sin(num));
}
sw.Stop();
PrintfFormat, TextWriter, Unit, Unit> format = new PrintfFormat, TextWriter, Unit, Unit, TimeSpan>("Run: %O");
PrintfModule.PrintFormatLineToTextWriter>(Console.Out, format).Invoke(sw.Elapsed);
sw.Restart();
GC.Collect(2);
sw.Stop();
format = new PrintfFormat, TextWriter, Unit, Unit, TimeSpan>("GC.Collect 2: %O");
PrintfModule.PrintFormatLineToTextWriter>(Console.Out, format).Invoke(sw.Elapsed);
ConsoleKeyInfo consoleKeyInfo = Console.ReadKey();
return 0;
}