C#性能问题

MrN*_*ick 9 c#

我刚刚参加了IKM C#测试.其中一个问题是:

以下哪项改进了C#程序的性能?

  • A.使用拳击
  • B.使用拆箱
  • C.不要使用常量
  • D.使用空的析构函数
  • E.使用值类型而不是引用类型

最后我跳过了这个问题,我能看到的唯一可能的答案是E.在某些情况下,值类型可以提供更好的性能(对于小类型:不需要解除引用而不在托管堆上[假设不是引用类型的成员] ]),但当然并非总是如此.

Han*_*ant 16

重点关注错误的答案:

当值类型值转换为引用类型值(对象)时,会发生装箱转换.它涉及从垃圾收集堆中分配内存,创建一个对象头,该对象头将对象标识为值类型的类型,并将值类型值位复制到对象中.这是一种转换,它创建类型系统错觉,值类型派生自System.ValueType和System.Object.拳击转换在.NET 1.x程序中大量使用,因为它支持System.Collections中的类,其元素为Object的集合所支持的唯一集合类型.在2.0中添加的.NET Generics使这些类立即过时,因为它允许在System.Collections.Generic中创建类.哪个可以存储值而无需将其存储起来.所以不行.

取消装箱是相反的转换,从盒装对象值返回到值类型值.不像装箱那么昂贵,它只涉及检查盒装对象的类型是否是预期类型并复制值类型值位.它需要在C#中进行强制转换,并且在盒装值类型不匹配时容易抛出异常.与前一个相同.

标有const关键字的标识符是直接编译为编译器生成的IL的文字值.另一种方法是readonly关键字.这需要内存访问来加载值,因此总是较慢.一个常量标识应始终是私有或内部,公共常量的打破,当你部署一个bug修复该改变的价值,但不重新编译使用常量的组件程序的诀窍.这些程序集仍将使用旧的常量值,因为它已编译到其代码中.readonly值不会发生的问题.所以不行.

析构函数(aka终结器)大大增加了对象的成本.垃圾收集器确保在对象被垃圾回收时调用终结器.但要这样做,它必须分别跟踪对象,这样的对象被放在终结器队列上,等待终结器线程绕过执行终结器.在下一次 GC传递之前,该对象实际上并未真正被销毁.您几乎总是拥有这样一个对象实现IDisposable的类,因此程序可以尽早调用终结器的职责,而不会给运行时自动负担.您在Dispose()方法中调用GC.SuppressFinalize().没有什么比没有做任何事情的终结者更糟糕,所以没有.

.NET中存在值类型,因为它们可以比引用类型更有效.它们的值比参考类型对象占用的内存少得多,并且可以存储在CPU寄存器和CPU堆栈中,这些内存位置在处理器设计中得到了高度优化.他们对语言设计进行了抽象处理,因为它们是抽象的,因为对象是一个泄漏的抽象,不可察觉地吞噬cpu循环,特别是结构是一种难以在你试图改变它们时打破程序的诀窍.但是,重要的是避免像Smalltalk这样的超纯语言遭受的那种性能损失.一种先驱的OOP语言,其中每个值都是一个对象,并影响了大量后续的OOP语言.但由于其性能不佳而很少实际使用,因为硬件工程师没有明确的路径使其与不抽象处理器设计的语言一样快.喜欢C#.所以这使它成为E.


Kev*_*Hsu 8

答案很可能是E.在几乎所有情况下,价值类型都会提高绩效.首先,在函数中使用值类型会创建堆栈空间,甚至在调用之前就会分配堆栈空间,从而避免了对象分配开销.其次,在堆上创建值类型数组时,都可以避免对象分配开销,并且数据往往更加缓存一致.

确实,在复制值类型时存在潜在的内存带宽开销,但现代内存带宽如此巨大,其损失通常会大大超过其他节省.另外,在处理64位或更小的类型时实际上没有损失.

  • 好吧,装箱,拆箱,没有常数和空的析构器可能会使性能变差.MSDN明确警告空析构函数的性能成本:[Destructors](http://msdn.microsoft.com/en-us/library/66x5fx1b.aspx) - "不应使用空的析构函数.当类包含析构函数时在Finalize队列中创建一个条目.当调用析构函数时,调用垃圾收集器来处理队列.如果析构函数为空,这只会导致不必要的性能损失." (3认同)