点击上方蓝字 江湖评谈设为关注
前言
上一篇:.NET9 Pre3 CLR的改进,讲了下.NET9 Pre3的CLR改进,第二个改进点即是内联。本篇详细看下,略有繁芜。
内联
Pre3里面,哪些代码可以内联优化呢?诸如以下这种:
typeof(T) == obj.GetType()
typeof(T) == typeof(T)
typeof(T) == null
obj1.GetType() == obj2.GetType()
比如其中的typeof,如果出现以下代码:
public static bool Callee<T>() => typeof(T) == typeof(int);
.NET9 Pre3之前呢?typeof函数它实际上是调用了System.Type.GetTypeFromHandle获取到类型,通过System.Type.op_Equality进行类型比较。则比较麻烦。
这里的优化是在JIT层面进行的,如果JIT检测到GetTypeFromHandle和op_Equality标记了Intrincis,则在JIT构建IR的时候,就把这个结果判断出来。
Importing BB01 (PC=000) of 'ConsoleApp3.Program:Callee[int]():ubyte'
[ 0] 0 (0x000) ldtoken
[ 1] 5 (0x005) call 0A00000E
In Compiler::impImportCall: opcode is call, kind=0, callRetType is ref, structSize is 0
Named Intrinsic System.Type.GetTypeFromHandle: Recognized
[ 1] 10 (0x00a) ldtoken
[ 2] 15 (0x00f) call 0A00000E
In Compiler::impImportCall: opcode is call, kind=0, callRetType is ref, structSize is 0
Named Intrinsic System.Type.GetTypeFromHandle: Recognized
[ 2] 20 (0x014) call 0A00000F
In Compiler::impImportCall: opcode is call, kind=0, callRetType is ubyte, structSize is 0
Named Intrinsic System.Type.op_Equality: Recognized
Importing Type.op_*Equality intrinsic
Folding call to Type:op_Equality to a simple compare via EQ
Optimizing compare of types-from-handles to instead compare handles
Asking runtime to compare 00007FF89EF9FC30 (System.Int32) and 00007FF89EF9FC30 (System.Int32) for equality
Runtime reports comparison is known at jit time: 0
[ 1] 25 (0x019) ret
STMT00000 ( 0x000[E-] ... ??? )
[000008] ----------- * RETURN int
[000007] ----------- \--* CNS_INT int 1
上面很清晰的看到在JIT Compile的时候就已经判断出了返回的值是0(false)。JIT判断代码:
const bool typesAreEqual = (s == TypeCompareState::Must);
const bool operatorIsEQ = (oper == GT_EQ);
const int compareResult = operatorIsEQ ^ typesAreEqual ? 0 : 1;
JITDUMP("Runtime reports comparison is known at jit time: %u\n", compareResult);
GenTree* result = gtNewIconNode(compareResult);
return result;
减少IR的代码,即如果泛型T与int同类型,则返回true,否则false。而不需要等到JIT生成机器码的时候,生成繁杂的GetTypeFromHandle和op_Equality机器码。
push rbp
mov rbp,rsp
mov eax,1 //类型相同为1,不同为0
pop rbp
ret
未优化前的:
ConsoleApp3.dll!ConsoleApp3.Program.Callee<T>():
//此处省略,便于观看
00007FF8BCAE22C3 FF 15 07 2C F4 FF call qword ptr [System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)+08h (07FF8BCA24ED0h)]
00007FF8BCAE22C9 48 89 45 E8 mov qword ptr [rbp-18h],rax
00007FF8BCAE22CD 48 B9 88 11 A5 BC F8 7F 00 00 mov rcx,7FF8BCA51188h
00007FF8BCAE22D7 E8 24 14 AE 5F call 00007FF91C5C3700
00007FF8BCAE22DC 48 89 45 E0 mov qword ptr [rbp-20h],rax
00007FF8BCAE22E0 48 8B 4D E0 mov rcx,qword ptr [rbp-20h]
00007FF8BCAE22E4 FF 15 E6 2B F4 FF call qword ptr [System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)+08h (07FF8BCA24ED0h)]
00007FF8BCAE22EA 48 89 45 D8 mov qword ptr [rbp-28h],rax
00007FF8BCAE22EE 48 8B 4D E8 mov rcx,qword ptr [rbp-18h]
00007FF8BCAE22F2 48 8B 55 D8 mov rdx,qword ptr [rbp-28h]
00007FF8BCAE22F6 FF 15 84 3C F4 FF call qword ptr [System.Type.op_Equality(System.Type, System.Type)+08h (07FF8BCA25F80h)]
//此处省略,便于观看
00007FF8BCAE230B C3 ret
未优化前,即使是删除了部分代码,依然是比较繁杂。
代码
代码(注意观察需要在Debug模式下开启Debuggable特性,也可以C# Code Relase放到CLR里面),参考:.NET9极致性能CLR操控MSIL(分层编译)。
可以加特性[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
或者dotnet build -c relase
namespace ConsoleApp3
{
public class Program
{
public static bool Test<T>() => Callee<T>();
public static bool Callee<T>() => typeof(T) == typeof(int);
static void Main(string[] args)
{
Test<int>();
Console.ReadLine();
}
}
}
原理
通过pMethod->GetMethodSignature().GetToken()获取方法Token
获取到System.Runtime.CompilerServices.IntrinsicAttribute这个特性
通过Token获取到CustomAttribute的IL表里面的Parent字段值,如果相等则表示Intrincis被设置了。此时就可以进行内联优化了。GetTypeFromHandle和op_Equality函数皆是如此。
往期精彩回顾