在进行JIT编译的时候,为了优化性能,会对一些方法做内联处理,MSDN上说下面这些情况不会内联:
-
IL 超过 32 字节的方法不会内联。
-
虚函数不会内联。
-
包含复杂流程控制的函数不会内联。复杂流程控制是除 if/then/else 以外的任意流程控制,在这种情况下,为 switch 或 while。
-
包含异常处理块的方法不会内联,但是引发异常的方法可以内联。
-
如果某个方法的所有定参都为结构,则该方法不会内联。
第一点很好理解了,之所以不让超过32字节的方法内联,是担心代码数量的膨胀。
虚函数不会内联。
虚函数的情形好理解,因为调用虚函数的汇编代码是这个样子的,因为虚函数是通过父类的指针调用子类的函数:
C#代码(TestMethod是一个虚函数):
汇编代码看起来是这样的:
mov ecx, [obj] --> 将obj的指针挪到寄存器里面,obj可能是一个子类的实例
add ecx, 4 --> 获取obj->TestMethod的虚函数表地址
mov eax, [ecx] --> 获取虚函数表的第一个虚函数地址,假定我们的TestMethod是第一个虚函数
call [eax] --> 调用虚函数
如果我们把call [eax]给内联了,就达不到多态的目的了。
如果某个方法的所有定参都为结构,则该方法不会内联。
我觉得这是因为结构式传值引用,在调用函数的时候,C#需要在堆栈(或者寄存器里面)分配结构的内存,并把参数内容拷贝过去,如果将定参为结构的函数都内联,可能会发生寄存器不够用的情况,那么频繁地从内存和寄存器之间传输数据已经抵消了内联带来的节省CALL和RET指令的好处。
包含异常处理块的方法不会内联
异常处理这玩意比较复杂,生成的CPU指令我还没怎么看过,不过不会内联应该是可以理解的,我查查资料看看。
为 switch 或 while
Switch也比较好理解,因为编译原理里面,switch语句的汇编指令生成算法比较复杂,一般是要创建一个排序数组来查找对应的Label的,所以不会内联也应该好理解,至于while为什么不会内联,我还真得要看看生成的汇编码。
我说的switch语句要生成一个排序数组的意思是,假设有一个像这样的switch语句:
switch ( i )
{
case 1:
// balabala
break;
case 2:
// balabala2
break;
default:
// bala
break;
}
生成的汇编代码类似于:
mov eax, [i] --> 将i的值保存到寄存器里面
// 这里只好写C#代码了,因为汇编代码我也忘记怎么写了
assembly label = BinarySearch(sortedCaseLabelArray[1, 2, ... ], eax)
// 这里是汇编代码
default: // bala
jmp EndOfSwitch
case_1: // balabala
jmp EndOfSwitch
case_2: // balabala2
jmp EndOfSwitch
EndOfSwitch
// 这里才是大括号
一般来说Switch语句都会大于32字节吧,JIT在生成代码的时候,怎么也得在JIT速度和生成代码的质量上找一个平衡的,所以一刀切是可以理解。