相信很有朋友在面试时大多会被问到 装箱
的问题,也是一个经典的问题,可深可浅,那今天我们就从 汇编
和 内存
角度进行统一解读下。
为了方便演示,先上一段装箱
的代码。
class Program{static void Main(string[] args){var i = 10;var o = (object)i;Console.ReadLine();}}
接下来用 windbg 看一下它的汇编代码。
0:000> !U /d 022e089a
Normal JIT generated code
ConsoleApp1.Program.Main(System.String[])
Begin 022e0848, size 5bD:\net5\ConsoleApp1\ConsoleApp1\Program.cs @ 15:
022e0872 c745f80a000000 mov dword ptr [ebp-8],0AhD:\net5\ConsoleApp1\ConsoleApp1\Program.cs @ 17:
022e0879 b9a8429e62 mov ecx,offset mscorlib_ni!System.Text.Encoding.GetEncodingCodePage(Int32)$##6006719 <PERF> (mscorlib_ni+0x142a8) (629e42a8) // 获取编码类型
022e087e e845282ffe call 005d30c8 (JitHelp: CORINFO_HELP_NEWSFAST) //生成一个初始化类型放在 eax 中。(objheader+methodtable+占位符)
022e0883 8945f0 mov dword ptr [ebp-10h],eax //备份地址到 栈中
022e0886 8b45f0 mov eax,dword ptr [ebp-10h] //恢复 eax 值
022e0889 8b55f8 mov edx,dword ptr [ebp-8] //将 0A 赋给 edx 上
022e088c 895004 mov dword ptr [eax+4],edx //将 edx 赋给 this.x 位置
022e088f 8b45f0 mov eax,dword ptr [ebp-10h] //提取栈值到 eax 值
022e0892 8945f4 mov dword ptr [ebp-0Ch],eax //将eax赋值给变量 o
因为每句汇编代码都有注释,我就不解释了,这里主要看一下 CORINFO_HELP_NEWSFAST
方法,它是干什么的呢?这得从源码说起:
/* Allocating a new object. Always use ICorClassInfo::getNewHelper() to decide which is the right helper to use to allocate an object of a given type. */CORINFO_HELP_NEW_CROSSCONTEXT, // cross context new objectCORINFO_HELP_NEWFAST,CORINFO_HELP_NEWSFAST, // allocator for small, non-finalizer, non-array objectCORINFO_HELP_NEWSFAST_FINALIZE, // allocator for small, finalizable, non-array objectCORINFO_HELP_NEWSFAST_ALIGN8, // allocator for small, non-finalizer, non-array object, 8 byte alignedCORINFO_HELP_NEWSFAST_ALIGN8_VC,// allocator for small, value class, 8 byte alignedCORINFO_HELP_NEWSFAST_ALIGN8_FINALIZE, // allocator for small, finalizable, non-array object, 8 byte alignedCORINFO_HELP_NEW_MDARR, // multi-dim array helper (with or without lower bounds - dimensions passed in as vararg)CORINFO_HELP_NEW_MDARR_NONVARARG,// multi-dim array helper (with or without lower bounds - dimensions passed in as unmanaged array)CORINFO_HELP_NEWARR_1_DIRECT, // helper for any one dimensional array creationCORINFO_HELP_NEWARR_1_R2R_DIRECT, // wrapper for R2R direct call, which extracts method table from ArrayTypeDescCORINFO_HELP_NEWARR_1_OBJ, // optimized 1-D object arraysCORINFO_HELP_NEWARR_1_VC, // optimized 1-D value class arraysCORINFO_HELP_NEWARR_1_ALIGN8, // like VC, but aligns the array startCORINFO_HELP_STRCNS, // create a new string literalCORINFO_HELP_STRCNS_CURRENT_MODULE, // create a new string literal from the current module (used by NGen code)
可以看到,CORINFO_HELP_NEWSFAST
是用于分配 小对象,无终结器,非数组
的专用方法,也属于高效的 快速分配路径
,那分配完之后的初始化长什么样子呢?这就需要用 windbg 下断点调试,从汇编代码看,最后的结果会存放在 eax
上, 如下图所示:
最后将栈上的10复制到堆上区域。
可以看到,这里涉及到了如下几个性能开销。
内存分配
风险在于分配引发的gc回收概率,比如判代回收 (临时代,FullGC)。
多次内存复制 (stack -> heap -> register)
一个装箱就有 6 个mov,反复的在 栈
,堆
,寄存器
之间交换。
增加 gc 回收压力
gc本来工作压力就很大,这又有无谓的分配,难哈。
最后就是如何解决,大概有如下两点。尽可能避免装箱 或者合理的使用 泛型
。