前言
VM 是 CLR 的一部分,但是它不包括 GC 和 JIT。VM 主要作用是进行类型的识别和DLL(托管以及非托管)的加载。 可以看到 VM
是一个比较重要的部分,.NET8
里面对它也进行了优化,属于核心级的优化。
概述
.NET8
里面针对 VM
的其中一个优化是将指令集映射到 MethodDesc
上,关于 MethodDesc
它是 CLR
里面所有托管方法的方法描述结构体。也即是描述方法所在的类,方法级别,方法是否被编译,方法签名信息,方法数据结构等等。
内存映射
关于内存映射这一点,之前提到过。参考文章:
- 《断点+内存映射终章(CLR问题)》https://mp.weixin.qq.com/s/kAQ2dkAVllWnBFexl6KTQg
- 《绝顶技术:断点+内存映射组合的超强BUG?》https://mp.weixin.qq.com/s/kAQ2dkAVllWnBFexl6KTQg
这种优化主要是针对比如堆栈的遍历,委托创建的时候,提高它们的转换性能,而且是 无锁的状态(性能提升点)
。从字面分析即可理解这种优化的程度是较高级别的。
1、委托创建的优化
看一个例子:
public class Tests
{
public void InSerial()
{
for (int i = 0; i < 10_000; i++)
{
CreateDelegate<string>();
}
}
public void InParallel()
{
Parallel.For(0, 10_000, i =>
{
CreateDelegate<string>();
});
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static Action<T> CreateDelegate<T>() => new Action<T>(GenericMethod);
private static void GenericMethod<T>(T t) {
}
}
比如以上委托创建的操作,.NET7
和 .NET8
性能对比:
Method | Runtime | Mean | Ratio |
---|---|---|---|
InSerial | .NET 7.0 | 1,868.4 us | 1.00 |
InSerial | .NET 8.0 | 706.5 us | 0.38 |
InParallel | .NET 7.0 | 1,247.3 us | 1. |
InParallel | .NET 8.0 | 222.9 us | 0.18 |
可以看到 .NET8
提升了 3
到 6
倍的性能。
2、分配器 ExecutableAllocator 的优化
另外一个优化是提高了 ExecutableAllocator
的性能,它叫做 分配器
。负责 CLR 中所有可执行内存相关的分配。比如说,JIT
用它来获取内存,把代码写入到获取的内存里,然后执行这些代码。当 ExecutableAllocator
获取的这段内存被映射的时候,它可以决定对这个内存进行读或者写,或者执行,或者其它的操作。ExecutableAllocator
维护一个缓存,通过减少缓存未命中的次数和减少这些缓存未命中时的成本来提高分配器的性能。
3、R2R 的优化
R2R
是预编译机器码的一种格式,AOT
编译和 JIT
编译是两个极端,一个是完全预编译,一个则是即时编译。R2R
介于这两者之间,如果检测到函数有预编译代码则进行 AOT
化的运行,如果没有则调用 JIT
进行即时编译运行。
这里优化的是 R2R
的启动时间,旨在减少 R2R
图像中验证类型所花费的时间,由于 R2R
图像中有专用的元数据,使得在 R2R
图像中查找泛型参数和嵌套类型变得更快,通过在方法描述中存储一个额外的索引,将 O(n^2)
的查找转变为 O(1)
的查找,以及确保 vtable
块始终被共享。
分配器 ExecutableAllocator
和 R2R
的优化是 C++
级的,托管的 C#
代码无法完全呈现,所以这里就不上代码了。了解这些优化即可,更深入可以参考之前的文章。
转载声明: