【问题标题】:Performance difference between generic and non generic method泛型和非泛型方法之间的性能差异
【发布时间】:2020-01-05 18:23:41
【问题描述】:

假设我们要处理线性代数,使用不同类型的矩阵。而且我们有一个自定义的 Matrix 类来实现:

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

我想实现矩阵乘法。我的印象是这两种方法:

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

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

(当然具有类似的实现)将在内部产生完全相同的 IL 并因此产生相同的性能。事实并非如此:第一个比第二个慢四倍。查看 IL,似乎泛型类似于通过接口调用:

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

我错过了什么吗?有什么方法可以让泛型获得与直接调用相同的性能?


已安装框架 4.8,目标框架:4.7.2(也使用 .Net Core 3 进行了测试)

方法实现:

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

【问题讨论】:

  • 您是否可以生成一个minimal reproducible example,其中包含一个包含两者的完全可执行示例?其他人更容易测试。
  • @LasseV.Karlsen 好主意,我已经编辑了这个问题,并附上了一个最小可复制示例的链接
  • 不应该,因为内部可能正在使用反射。
  • 存在装箱操作,当您使用泛型变体中的索引器访问矩阵元素时,IL 代码显示box !!0/*TMatrix*/ 指令。这可能是性能下降的一个原因,因为装箱是非常昂贵的操作。但现在不能肯定地说,为什么在你的代码中会发生装箱
  • @FrédéricDelanchy 有一个现有的thread 将解释为什么对泛型类型参数执行装箱操作。这以及我之前的评论可能是性能差异的原因。非泛型变体中没有装箱操作

标签: c# .net generics .net-core .net-4.7.2


【解决方案1】:

.NET 只会为所有引用类型生成一次泛型方法的代码。并且该代码必须通过IMatrix 接口调用,因为各种实现类型可能使用不同的方法实现该接口。所以它只是一个接口调用。

但是,如果您将 Matrix 设为 struct 而不是 class,则 JITter 将生成泛型方法的特定于类型的实现,并且可以优化接口调用。

【讨论】:

  • 刚刚测试过:它有效。你知道为什么它不能使用引用类型生成特定类型的实现吗?
  • 这是 JIT 中的性能权衡。想象一下,如果List&lt;T&gt; 为每个类型参数生成不同的代码。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-16
  • 2015-07-30
相关资源
最近更新 更多