浅谈装箱与拆箱的性能损失
1 首先先来谈下值类型与引用类型
引用类型:引用类型包含指向存储数据的其他内存位置的指针,引用类型总是从托管堆上分配。c#的new操作符返回的就是对象位于托管堆中的内存地址—该内存地址指向对象占用数据位。使用引用类型是我们必须考虑一些性能问题:
l 内存必须从托管堆上分配。
l 每个在托管堆中分配的对象都有一些与之关联的额外附加成员必须被初始化。
l 从托管堆中分配对象可能会导致执行垃圾收集器。
值类型:值类型实例在它自己的内存分配中存储数据,通常分配在线程的堆栈上。表示值类型实例的变量不包含指向实例的指针,变量本身即包含了实例的所有字段,操作实例时无需再解析指针引用。值类型实例不受垃圾收集器的控制,因此减少了托管堆的压力,以及应用程序在整个生存周期中需要垃圾回收的次数。
2 接着看装箱与拆箱
装箱:将一个值类型转换为一个引用类型。装箱操作通常有以下几步组成:
1. 从托管堆中为新生成的引用类型对象分配内存。分配内存的大小为值类型实例本身的大小加上其他额外的将值类型实例视为真正的引用类型所需的空间,这些额外的空间包括1个方法表指针和一个SyncBlockIndex。
2. 将值类型的实例字段拷贝到托管堆上新分配的对象内存中。
3. 返回托管堆中新分配对象的地址。该地址就是一个指向对象的引用。值类型实例也就变成了一个引用类型对象。
拆箱:从引用类型中获取指向对象中包含的值类型部分(数据字段)的指针,拆箱仅仅到此。然而紧接着拆箱之后典型的操作往往就是字段的拷贝。
由此可见,装箱和拆箱/拷贝操作会从速度和内存两个方面损伤应用程序的性能。因此我们应该清楚编译器会在何时自动产生执行这些操作的指令,并使我们编写的代码尽可能减少导致这种情况发生的机会。