Today I ran into this issue: When using reference types as type arguments for a outer generic type, other methods in nested types are slower by a factor ~10. It does not matter which types I use - all reference types seem to "slow" the code down. (Sorry for the title, maybe somebody can find a more suitable one.)
Tested with .NET 5/Release builds.
What am I missing?
EDIT 2:
I'll try to explain the problem a little bit further and cleanup the code. If you still want to see the old version, here is a copy:
https://gist.github.com/sneusse/1b5ee408dd3fdd74fcf9d369e144b35f
The new code illustrates the same issue with hopefully less distraction.
- The class
WthGeneric<T>
is instantiated twice - The first instance uses any reference type as the type argument (here:
object
) - The second instance uses any value type as the type argument (here:
long
) - As both are instances of the same class both have the same method
WhatIsHappeningHere
- Neither of the instances uses the generic argument in any way.
This leads to the question: Why is the runtime of the same instance method 10x higher than the other one?
Output:
System.Object: 516,8448ms
System.Int64: 50,6958ms
Code:
using System;
using System.Diagnostics;
using System.Linq;
namespace Perf
{
public interface IWthGeneric
{
int WhatIsHappeningHere();
}
// This is a generic class. Note that the generic
// type argument 'T' is _NOT_ used at all!
public class WthGeneric<T> : IWthGeneric
{
// This is part of the issue.
// If this field is not accessed or moved *outside*
// of the generic 'WthGeneric' class, the code is fast again
// ** also with reference types **
public static int StaticVar = 12;
static class NestedClass
{
public static int Add(int value) => StaticVar + value;
}
public int WhatIsHappeningHere()
{
var x = 0;
for (int i = 0; i < 100000000; i++)
{
x += NestedClass.Add(i);
}
return x;
}
}
public class RunMe
{
public static void Run()
{
// The interface is used so nothing could ever get inlined.
var wthObject = (IWthGeneric) new WthGeneric<object>();
var wthValueType = (IWthGeneric) new WthGeneric<long>();
void Test(IWthGeneric instance)
{
var sw = Stopwatch.StartNew();
var x = instance.WhatIsHappeningHere();
Console.WriteLine(
$"{instance.GetType().GetGenericArguments().First()}: " +
$"{sw.Elapsed.TotalMilliseconds}ms");
}
for (int i = 0; i < 10; i++)
{
Test(wthObject);
Test(wthValueType);
}
}
}
}