.NET8极致性能优化Non-GC Heap

前言

.NET8里面JIT引入了一个新的机制,叫做Non-GC Heap。JIT可以确保相关对象分配在Non-GC Heap上,该堆像其名称一样,不受GC管理。JIT需要保证这个对象没有被GC引用,并且在这个对象的生命周期内一直是根对象(不会被GC消灭的对象)的状态。原文:.NET8极致性能优化Non-GC Heap

概述

为什么要引入这种机制?先来看一段代码:

public static string GetPrefix() => "https://";
static void Main(string[] args)
{
  GetPrefix ();
}

这里的GetPrefix函数返回的是一个常量字符串值,它的ASM如下:

mov  rax,185CAC02068h
mov  rax,qword ptr [rax]

两个mov指令,第一个是对象指针的指针,第二个是对象的指针。虽然是简单的两个指令,但是背后的逻辑却较为复杂,基本如下:
一个字符串常量值,.NET7里面JIT也会给这个字符串常量值复制到一个堆分配到字符串对象中,返回的是对象的二级指针。因为是堆对象,可能会被GC移动,每次都需要获取新的地址,频繁增加负担。

这里的问题在哪儿呢?一个字符串常量值需要这么多的步骤操作吗?开销是否太大,我们是否可以简化它呢?有一个常规的很容易想到的方法,就是把这个字符串常量值的地址给它固定起来,每次需要用到这个常量值,就直接去这个固定地址读取,这样行不行呢?GC堆很明显不能硬编码固定。

当然可以,做法就是把这个字符串常量值放到POH(固定对象堆)上,不让GC移动。这样是减少了GC回收的时候移动的开销,但是并没有从根本上解决问题,因为固定对象同样受到GC的管控,上面的步骤除了不能移动一样不少,并且POH不会进行根对象的处理,可能会导致它们被回收,地址指向了其它的数据,进而错误。

特点

要彻底的解决这个问题,本篇的主角:Non-GC Heap出场了。它有三个特点:
1.JIT要保证这个对象没有被GC引用
2.这个对象在生命周期内一直是根对象
3.它不能是可卸载上下文的一部分

你可以认为GC堆包括:小对象堆(SOH-小于85000字节的对象),大对象堆(LOH-大于85000字节的对象),固定对象堆(POH)
而No-GC Heap超脱于GC Heap之外的FOH(冻结堆)。

JIT现在可以避免在生成的代码中访问该对象时的间接寻址,而是直接硬编码对象的地址

GetPrefix函数的ASM在.NET8 Non-GC Heap里面如下:

mov  rax,26180000218h
C3   ret

26180000218h为对象地址,一个mov直接返回。看似只简化了一个mov,但是实际上它这种硬编码固定模式地址,简化的是整个字符串常量值的原理,也就是把字符串常量值分配到FOH里面,而不是GC堆里。性能极大的提升自不必多说。以下测量13倍的性能提升。

Method Job Mean Ratio
GetPrefix .NET 7 1.3450 ns
GetPrefix .NET 8 0.0729 ns

其它Non-GC Heap的操作

一:使用typeof(T)生成的RuntimeType对象

public Type GetTestsType() => typeof(Tests);

二:空数组分配到Non-GC Heap上,使Array.Empty()更加高效

public string[] Test() => Array.Empty<string>();

它俩在.NET8里面都类似于如下ASM,一个mov直接返回:

mov rax,1A0814EAEA8
ret

三:静态值类型字段关联的堆对象,不包含任何GC引用的字段

public partial class Tests
{
    private static readonly ConfigurationData s_config = ConfigurationData.ReadData();
    public TimeSpan GetRefreshInterval() => s_config.RefreshInterval;
    private struct ConfigurationData
    {
        public static ConfigurationData ReadData() => new ConfigurationData
        {
            Index = 0x12345,
            Id = Guid.NewGuid(),
            IsEnabled = true,
            RefreshInterval = TimeSpan.FromSeconds(100)
        };
        public int Index;
        public Guid Id;
        public bool IsEnabled;
        public TimeSpan RefreshInterval;
    }
}

RefreshInterval .NET7如下:

mov       rax,13D84001F78
mov       rax,[rax]
mov       rax,[rax+20]
ret

RefreshInterval .NET8如下:

mov       rax,20D9853AE48
mov       rax,[rax]
ret

四:代之间的GC引用判断

代码:

public class Tests
{
    public void Write()
    {
        string dst = "old";
        Write(ref dst, "new");
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static void Write(ref string dst, string s) => dst = s;
}

Write在.NET7和.NET8上生成如下:

call      CORINFO_HELP_CHECKED_ASSIGN_REF
nop
ret

CORINFO_HELP_CHECKED_ASSIGN_REF是一个JIT帮助程序函数,其中包含所谓的“GC write barrier (GC写屏障)”,一个小代码片段,用于让GC跟踪正在写入的引用,因为它可能需要知道,例如,因为正在分配的对象可能是gen0,而目标可能是gen2。

微调下这个代码:

public class Tests
{
    public void Write()
    {
        string dst = "old";
        Write(ref dst);
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static void Write(ref string dst) => dst = "new";
}
  • 实现的功能都是一样的,只不过dst直接赋值了常量字符串,记得上面常量字符串的分配是在Non-GC Heap吗?.NET7里面还是需要帮助函数:
mov       rdx,1FF0E4014A0
mov       rdx,[rdx]
call      CORINFO_HELP_CHECKED_ASSIGN_REF
nop
ret

然.NET8里面则是

mov       rax,1B3814EAEC8
mov       [rcx],rax
ret

因为.NET8意识到常量字符串是在Non-GC Heap,不需要GC跟踪判断在那个代码,类似于card_table那种。所以优化掉了CORINFO_HELP_CHECKED_ASSIGN_REF

往期精彩回顾:

.NET8 JIT核心:分层编译的原理

新版.Net性能有没有达到C++90%?

面试官问.Net对象赋值为null,就会被GC回收吗?

CLR Via C#还值得一读吗

骚操作:托管DLL四位.Net创始人名字首字母

.Net JIT的骚操作DNGuard HVM原理简析

作者:江湖评谈。公众号:jianghupt.欢迎关注。文章首发地。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/182350.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【iOS】数据持久化(二)之归档和解档(iOS 13以后)

在之前介绍的数据存储方法中&#xff0c;不管是NSUserDefaults还是plist文件都不能对自定义对象进行存储&#xff0c;OC提供的解归档恰好解决了这个问题 本片文章对 iOS13 以后的版本 归档和解档 进行介绍。老版本的解归档见这篇文章&#xff1a;【iOS】文件&#xff08;对象数…

【Java】认识String类

文章目录 一、String类的重要性二、String类中的常用方法1.字符串构造2.String对象的比较3.字符串查找4.转换5.字符串替换6.字符串拆分7.字符串截取8.其他操作方法9.字符串的不可变性10.字符串修改 三、StringBuilder和StringBuffer 一、String类的重要性 在C语言中已经涉及到…

微型计算机原理MOOC题

一、8254 1.掉坑了&#xff0c;AL传到端口不意味着一定传到的是低位&#xff0c;要看控制字D5和D4&#xff0c;10是只写高位&#xff0c;所以是0A00.。。 2. 3. 4.待解决&#xff1a;

leetCode 2925. 在树上执行操作以后得到的最大分数 + 正则难反 + 树形 DP

2925. 在树上执行操作以后得到的最大分数 - 力扣&#xff08;LeetCode&#xff09; 有一棵 n 个节点的无向树&#xff0c;节点编号为 0 到 n - 1 &#xff0c;根节点编号为 0 。给你一个长度为 n - 1 的二维整数数组 edges 表示这棵树&#xff0c;其中 edges[i] [ai, bi] 表示…

【从零开始实现意图识别】中文对话意图识别详解

前言 意图识别&#xff08;Intent Recognition&#xff09;是自然语言处理&#xff08;NLP&#xff09;中的一个重要任务&#xff0c;它旨在确定用户输入的语句中所表达的意图或目的。简单来说&#xff0c;意图识别就是对用户的话语进行语义理解&#xff0c;以便更好地回答用户…

JS数组常用的20种方法详解(每一个方法都有例子,超全面,超好理解的教程,干货满满)

目录 1.会改变原数组的方法&#xff08;7种&#xff09; 1.push() 2.pop() 3.unshift() 4.shift() 5.reverse() 6.sort() 7.splice() 2.不改变原数组的方法&#xff08;13种&#xff0c;返回的新数组是从原数组浅拷贝来的&#xff09; 1.concat() 2.join() 3.slice…

九、ffmpeg命令转封装

开了几天小差&#xff0c;今天继续学习ffmpeg。 准备测试使用的视频&#xff0c;并查看其信息 # 查看视频信息。使用Mediainfo也可以 ffprobe test.mp4 视频格式的信息如下。 保持编码格式&#xff1a;ffmpeg -i test.mp4 -vcodec copy -acodec copy test_copy.tsffmpeg -i…

2015年2月4日 Go生态洞察:Go语言中的包命名艺术

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

王道p150 20.将给定的表达式树转化为等价的中缀表达式(通过括号反应操作符的计算次序)

本题代码如下 void btreetoexp(tree t, char deep) {if (t NULL)return;else if (t->lchild NULL && t->rchild NULL)printf("%c", t->data);//输出操作数&#xff0c;不加括号else {if (deep > 1)printf("(");btreetoexp(t->l…

231123 刷题日报-动态规划

今天主要看了DP&#xff0c;前几天频繁遇到DP打击有点大。。 1. 0-1背包问题 要点&#xff1a; a. 三部曲&#xff1a; 1. 状态和选择 状态&#xff1a;物品序号、背包容量 选择&#xff1a;放、不放 2. dp数组定义、base case dp[i][w] 对于前i个物品&#xff0c;当前背包…

UNETR:用于三维医学图像分割的Transformer

论文链接&#xff1a;https://arxiv.org/abs/2103.10504 代码链接&#xff1a; https://monai.io/research/unetr 机构&#xff1a;Vanderbilt University, NVIDIA 最近琢磨不出来怎么把3d体数据和文本在cnn中融合&#xff0c;因为确实存在在2d里面用的transformer用在3d里面…

leetcode刷题之用栈实现队列(C语言版)

leetcode刷题之用栈实现队列&#xff08;C语言版&#xff09; 一、题目描述二、题目要求三、题目解析Ⅰ、typedef structⅡ、MyQueue* myQueueCreateⅢ、void myQueuePush(MyQueue* obj, int x)Ⅳ、int myQueuePeek(MyQueue* obj)Ⅴ、int myQueuePop(MyQueue* obj)Ⅶ、bool myQ…

编译器核心技术概览

编译技术是一门庞大的学科&#xff0c;我们无法对其做完善的讲解。但不同用途的编译器或编译技术的难度可能相差很大&#xff0c;对知识的掌握要求也会相差很多。如果你要实现诸如 C、JavaScript 这类通用用途语言&#xff08;general purpose language&#xff09;&#xff0c…

[shader] 光照入门(未完结。。。

反射 漫反射&#xff1a;而当物体表面粗糙时&#xff0c;我们把物体表面看作无数不同方向的微小镜面&#xff0c;则这些镜面反射出的光方向均不相同&#xff0c;这就是漫反射。 高光反射&#xff1a;我们假定物体表面光滑&#xff0c;只有一个镜面&#xff0c;那么所有的光都…

微信小程序前端环境搭建

搭建微信小程序前端环境 申请小程序测试账号 访问路径 使用微信扫描二维码进行申请&#xff0c;申请成功之后&#xff0c;进入界面&#xff0c;获取小程序ID(AppID)和秘钥(AppSecret) 安装微信web开发者工具 访问路径 选择稳定开发的版本 需要在小程序的设置中将默认关闭…

深入理解JVM 类加载机制

深入理解JVM 类加载机制 虚拟机如何加载Class文件&#xff1f; Class文件中的信息进入到虚拟机后会发生什么变化&#xff1f; 类加载机制就是Java虚拟机把描述类的数据从Class文件加载到内存&#xff0c;并对数据进行校验、转换解析和初始化&#xff0c;最终形成可以被虚拟机…

AMEYA360:瑞萨面向高端工业传感器系统推出高精度模拟前端的32位RX MCU

全球半导体解决方案供应商瑞萨电子&#xff08;TSE&#xff1a;6723&#xff09;宣布面向高端工业传感器系统推出一款全新RX产品——RX23E-B&#xff0c;扩展32位微控制器&#xff08;MCU&#xff09;产品线。新产品作为广受欢迎的RX产品家族的一员&#xff0c;具有高精度模拟前…

3D火山图绘制教程

一边学习&#xff0c;一边总结&#xff0c;一边分享&#xff01; 本期教程内容 **注&#xff1a;**本教程详细内容 Volcano3D绘制3D火山图 一、前言 火山图是做差异分析中最常用到的图形&#xff0c;在前面的推文中&#xff0c;我们也推出了好几期火山图的绘制教程&#xff0…

如何通过宝塔面板搭建一个本地MySQL数据库服务并实现远程访问

宝塔安装MySQL数据库&#xff0c;并内网穿透实现公网远程访问 文章目录 宝塔安装MySQL数据库&#xff0c;并内网穿透实现公网远程访问前言1.Mysql服务安装2.创建数据库3.安装cpolar3.2 创建HTTP隧道 4.远程连接5.固定TCP地址5.1 保留一个固定的公网TCP端口地址5.2 配置固定公网…

Axios 通过a标签下载文件 跨域下载

<!-- a标签占位 --><a ref"down" ></a>getTest() {this.$axios.request({url: https://cnv13.55.la/download?file_key3695fa9461a0ae59cf3148581e4fe339&handle_typeexcel2pdf,method: get,responseType: blob, // 切记类型 blob}).then(re…
最新文章