【问题标题】:Normalizing .net generics规范化 .net 泛型
【发布时间】:2009-10-08 19:35:24
【问题描述】:

在重写程序集时,如果我要指示编译器为每个泛型实例生成一个非泛型类型,应用程序是否会在代码中变得更大而具有相同的性能?

【问题讨论】:

  • 我……不明白这个问题。能改一下吗?
  • 同意 Randolpho...现在的问题没有多大意义。
  • 我认为他的意思是你说 List 它会有效地创建一个 PersonCollection 类来代替它。不过我不知道答案:-)
  • 您想知道是否会通过生成专用类型而不是通用类型来获得性能优势(例如 C++ 中的模板如何工作)?
  • 是的 - 生成专门的类型

标签: .net compiler-construction cil


【解决方案1】:

在 JIT 之前或之后修改已编译代码的大小可能会对性能产生影响。

例如,增加可执行代码的数量会导致额外的缓存未命中,或占用更多的虚拟内存。这两种情况都会减慢程序的执行速度。

此外,增加程序集中存在的命名类型的数量可能会减慢运行时间。如果运行时必须检查更大的数据结构来解析类型名称,那么解析类型名称和执行 JIT 编译可能需要更长的时间。

最后,如果它们对 JIT 来说是更多类型,那么 CLR 最终可能会花费大部分时间来生成代码。

您需要进行测试才能确定,但​​我的猜测是,做您所说的事情会产生净负面影响。

不过,CLR 对它生成的通用代码非常聪明。泛型方法的不同引用类型实例化最终使用大部分相同的本机方法主体,除了需要加载类型标记的部分。然而,值类型实例化最终会获得自己独特的本地方法体。

这通常会提供良好的性能。它平衡了代码膨胀对性能的影响与额外装箱对性能的影响。

您当然可以构建 CLR 设计会导致非最佳性能的工作负载。然而,这些案例对我来说似乎很病态。我敢打赌,对于大多数真实代码,直接使用 CLR 泛型会获得更好的性能。

【讨论】:

    【解决方案2】:

    不,代码和性能实际上是相同的。

    编译器已经生成了特定的类。我不确定语言编译器做了多少工作,JIT 编译器做了多少工作,但最终结果几乎是一样的。

    【讨论】:

      【解决方案3】:

      泛型可以通过减少方法体中发生的强制转换和自动装箱(分别用于引用和值类型)操作的数量来获得一些性能。

      MSDN article on generics.

      “无通用”方法实现不应该在性能方面为您带来任何好处,因为 JIT 将为您处理生成任何特定用例并积极缓存它们。

      【讨论】:

        【解决方案4】:

        与非泛型等效项相比,泛型提供了性能和运行时内存优势。例如,比较以下内容。

        ArrayList arrayList = new ArrayList(new Byte[] { 1, 2, 3, 4, 5, 6, 7, 8} );
        

        List<Byte> byteList = new List<Byte> { 1, 2, 3, 4, 5, 6, 7, 8 };
        

        ArrayList 将包含 object[] 的内部存储,其中每个元素都指向一个字节。因此,具有 32 位指针的这部分列表的理想内存是 8 * 4 + 8 = 40 字节。我说理想,因为它实际上更糟,因为盒装字节会有一些额外的开销,尽管我不知道具体有多少。

        List 实现将改为包含 byte[],它只需要一个指向数组的指针加上 8 个字节,总共 12 个字节的内存没有装箱开销,不管是什么。

        除了内存差异之外,还存在装箱/拆箱值的性能成本。

        引用类型的差异较小,但即便如此,当从非泛型类型中取出数据时,您也会因为不断地进行转换而付出代价。

        这些差异是真实存在的,但会对大多数应用程序产生不可估量的影响。泛型的真正优势是编译时验证的更高可靠性和使用类型值产生的更简单的代码。

        在我看来,最好在适当和可能的情况下使用泛型。

        山姆

        【讨论】:

          【解决方案5】:

          这个问题很有道理。

          关于您唯一有保证的好处是更少的元数据或更少的运行时解析,显然更少的键盘输入和更多的 DRY。但是,这种情况与重新启动的 C++ 编译器和 Bjarne 多年来一直抱怨的事情非常相似。绝对不需要为指针容器生成膨胀..

          无论如何,C# 泛型是一种非常糟糕的语言和运行时特性,可能会引起很多麻烦,并且没有接口几乎没用。无法进行值类型比较是设计相当糟糕的一个明显例子,以及跨翻译单元等使用使用(无 typedef)的大量 DRY。

          【讨论】:

          • -1:会有更多元数据,而实现实际上会更加臃肿。
          • hmm.. 所以如果所有客户端现在都必须编写 StringList、IntList、Z28280List 和 x 其他以及 MinusOneList,我最终得到的元数据比使用 List.. 的要少数学?
          • 类似的适用于 JIT 生成的代码大小,thunk,也很简单.. 作为大师参考,我会选择 The C++ Programming Language 并查看一些关于专业化的奇怪章节,其中展示了它如何规避愚蠢的编译器和臃肿。