在重写程序集时,如果我要指示编译器为每个泛型实例生成一个非泛型类型,应用程序是否会在代码中变得更大但具有相同的性能?
5 回答
修改编译代码的大小,无论是在 JIT 之前还是之后,都会对性能产生影响。
例如,增加可执行代码的数量可能会导致额外的缓存未命中,或者使用更多的虚拟内存。这两种情况都会减慢程序的执行速度。
此外,增加程序集中存在的命名类型的数量可能会减慢运行时间。如果运行时必须检查更大的数据结构来解析类型名称,那么解析类型名称和执行 JIT 编译可能需要更长的时间。
最后,如果它们是 JIT 的更多类型,那么 CLR 最终可能会花费大部分时间来生成代码。
您需要进行测试才能确定,但我的猜测是,做您所说的事情会产生净负面影响。
不过,CLR 对它生成的通用代码非常聪明。泛型方法的不同引用类型实例化最终使用大部分相同的本机方法主体,除了需要加载类型标记的部分。然而,值类型实例化最终会获得它们自己独特的本地方法体。
这通常提供良好的性能。它平衡了代码膨胀的性能影响与额外装箱的性能影响。
您当然可以构建 CLR 设计会导致非最佳性能的工作负载。然而,这些案例对我来说似乎很病态。我敢打赌,对于大多数真实代码,直接使用 CLR 泛型会获得更好的性能。
不,代码和性能实际上是相同的。
编译器已经生成了特定的类。我不确定语言编译器做了多少工作以及 JIT 编译器做了多少工作,但最终结果实际上是相同的。
泛型可以通过减少方法体中发生的强制转换和自动装箱(分别用于引用和值类型)操作的数量来获得一些性能。
“无通用”的方法实现不应该在性能方面为您带来任何好处,因为 JIT 将为您处理生成任何特定用例并积极缓存它们。
与非泛型等效项相比,泛型提供了性能和运行时内存优势。例如,比较以下内容。
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 个字节的内存没有装箱开销,不管是什么。
除了内存差异之外,还存在装箱/拆箱值的性能成本。
引用类型的差异较小,但即便如此,当从非泛型类型中取出数据时,您也会因为不断地进行转换而付出代价。
这些差异是真实存在的,但会对大多数应用程序产生不可估量的影响。泛型的真正优势是编译时验证的更高可靠性和使用类型值产生的更简单的代码。
在我看来,最好在适当和可能的情况下使用泛型。
山姆
这个问题是有道理的。
唯一可以保证的好处是运行时更少的元数据或更少的解析,显然更少的键盘输入和更多的 DRY。但是,这种情况与重新启动的 C++ 编译器和 Bjarne 多年来一直抱怨的事情非常相似。绝对不需要为指针容器生成膨胀..
无论如何,C# 泛型是一种非常糟糕的语言和运行时特性,可能会引起很多麻烦,并且没有接口几乎没用。无法进行值类型比较是设计相当糟糕的一个明显例子,以及大量 DRY跨翻译单元使用 usings(无 typedef)等。