3

Let's say for the sake of the example that we want to work on linear algebra, with different type of matrices. And that we have a custom Matrix class that implements :

interface IMatrix
{
    double this[int i, int j] { get; set; }
    int Size { get; }
}

I want to implement Matrix multiplication. I was under the impression that both methods :

static void Multiply<TMatrix>(TMatrix a, TMatrix b, TMatrix result) where TMatrix : IMatrix

and

static void Multiply(Matrix a, Matrix b, Matrix result)

(with similar implementation of course) Would internally produce the exact same IL and hence the same performances. It is not the case : the first one is four times slower than the second one. Looking at the IL, it seems the generic one is similar to a call through interface :

static void Multiply(IMatrix a, IMatrix b, IMatrix result)

Am I missing something ? Is there any way to get the same performances with generics than with a direct call ?


Installed Framework 4.8, Target Framework : 4.7.2 (also tested with .Net Core 3)

Method implementation :

static void Multiply(Matrix a, Matrix b, Matrix result)
{
    for (int i = 0; i < a.Size; i++)
    {
        for (int j = 0; j < a.Size; j++)
        {
            double temp = 0;
            for (int k = 0; k < a.Size; k++)
            {
                temp += a[i, k] * b[k, j];
            }
            result[i, j] = temp;
        }
    }
}

Minimal reproductible example

  • Possible for you to produce a [mcve] containing a fully executable example that includes both? Easier for others to test. – Lasse V. Karlsen Jan 05 '20 at 18:33
  • @LasseV.Karlsen good idea, I've edited the question with a link to a minimal reproductible example – Frédéric Delanchy Jan 05 '20 at 19:11
  • It shouldn't since reflection is probably being used internally. – SILENT Jan 05 '20 at 19:40
  • There is boxing operation, when you access a matrix element using indexer in generic variant, IL code shows `box !!0/*TMatrix*/` instructions. It can be a reason of performance drawback, since boxing is quite expensive operation. But can't say for sure right now, why boxing is happens in your code – Pavel Anikhouski Jan 05 '20 at 20:30
  • @FrédéricDelanchy there is an existing [thread](https://stackoverflow.com/questions/20683715/why-is-box-instruction-emitted-for-generic) which will explain, why boxing operation is performed for generic type argument. This, as well as my previous comment, can be a reason of performance difference. There is no boxing operation in non-generic variant – Pavel Anikhouski Jan 05 '20 at 20:51
  • @PavelAnikhouski I forgot to specify that even if my project targets the 4.7.2 .Net Framework, the installed Framework on my computer is the 4.8. I don't see any boxing in IL. – Frédéric Delanchy Jan 05 '20 at 21:15

1 Answers1

4

.NET will only generate code for a generic method once for all reference types. And that code must call through the IMatrix interface, as the various implementing types might implement the interface with different methods. So it simply is an interface call.

However if you make Matrix a struct instead of a class the JITter will generate a type-specific implementation of the generic methods, and in that the interface call can be optimized away.

David Browne - Microsoft
  • 80,331
  • 6
  • 39
  • 67