AOT和单文件发布对程序性能的影响

前言

这里先和大家介绍一下.NET一些发布的历史,以前的.NET框架原生并不支持最终编译结果的单文件发布(需要依赖第三方工具),我这里新建了一个简单的ASP.NET Core项目,发布以后的目录就会像下图这样,里面包含很多*.dll文件和其它各类的文件。
c5060503d51461a8e431fda7034aacb8.png

在.NET Core 2.1时代,引入了单文件发布的功能,只需要在发布命令上,增加-p:PublishSingleFile=true参数就可以使用,从这以后就无需发布的文件夹就再也没有那么多的文件,只有一个*.exe文件和对应的配置文件和用于调试*.pdb的文件,如下所示:
7cf4343cfaaa6412f434dfe741534b1a.png
不过此时的.NET还是需要安装一个大小为50~130MB左右的.NET Runtime才能运行,这个其实不利于在客户端场景下程序的分发,大家应该能回忆起在安装一些软件之前,必须安装.NET Framework的场景。
8b6aed0b08b11b839c88db4e3c7034df.png

在单文件发布推出的同时,也可以通过--self-contained true的参数,将运行时也包含在发布文件内,这样的话就无需在目标机器上再安装.NET Runtime。不过由于它自带运行时,整个发布文件夹的大小就变得很大了,可以说比安装.NET Runtime还要大一些(足足82.4MB)。
cecab2a039f6624c7f5b7504ad54fe2c.png
程序本质上也就是文件,我们也可以通过压缩程序的方式,让它的大小变小,只需要加上-p:EnableCompressionInSingleFile=true参数。就可以将80MB的程序压缩至44MB左右。
30a6787d1576ebc8a20f6fd55498f03d.png

单文件发布体积大的原因就是包括了所有运行可能用到的依赖,不过有很多依赖是我们程序中用不到的,所以发布的时候可以加-p:PublishTrimmed=true参数,发布的时候移除掉没有使用的依赖,这样体积就可以降低很多(从44MB到35MB)。
5baa7fd00541079017d31c7cf1faa0e0.png

当然,移除没有使用的依赖和压缩是可以同时使用的,这样发布以后,体积就可以变得更小了,只需要20MB左右。
b3c29361a929962bf50a0943f214ce4d.png
此时.NET运行还是需要自带运行时,在运行.NET程序的时候需要JIT来参与,这样的话在应用启动时需要一定的时间让JIT将MSIL编译到对应平台机器码,随后.NET推出了预览版的Native-AOT,可以在编译时直接将代码编译成对应平台的机器码,以加快启动速度;另外由于不需要自带运行时,它整个的体积大小也变得很小。
0432d45878ed56b81a9d7d42224567d2.png
用于调试的pdb文件就会变得很大,不过真实发布的话也用不到这个文件,可以舍弃。AOT以后的大小也就20MB左右。不过AOT也不是银弹,由于没有了JIT,很多编译时优化就不能做了,Java的GraalVm发布的时候就有一张五边形图,充分的说明了JIT和AOT之间的取舍。
0323ec3cabce0f6cecd3957ae8821a9e.png
AOT拥有更快的启动速度、更低的内存占用和更小的程序体积;当然它的吞吐量和最大延时表现的就没那么好(另外也会失去很多动态的特性,降低一些编程效率)。

心中会有一个疑问,这样的发布方式会对程序的性能有影响嘛?都说AOT会让程序启动速度变快,那么会变快多少呢?

评测结果

我决定花点时间来研究一下,周末带着上面的问题我设计了一组测试,当然时间仓促有很多不严谨的地方,可以说就图一乐,望大家指出和海涵。一共设计了12个组,主要是对比单文件发布、AOT发布和普通发布的区别;另外我也加入了PGO、TC、OSR和OSA等JIT参数,来看看不同JIT参数的影响。

PGO:PGO 即 Profile Guided Optimization(配置引导优化),通过收集运行时信息来指导 JIT 如何优化代码,相比以前没有 PGO 时可以做更多以前难以完成的优化。可以参考hez大佬的博客,还有一些链接1、链接2、链接3.

TC:TC 即 Tiered Compilation(分层编译),是一种运行时优化代码的技术,每个C#函数都会由JIT编译成目标平台的机器码,为了让方法能快点运行,JIT一般会很粗犷(并不是最优,生成代码效率比较低)的编译,所以JIT就引入了TC,当某一个方法频繁被调用时,JIT就会为它编译一份更优的代码,这样下一次方法被调用时,它执行的会更有效率。想了解更多关于.NET分层编译可以戳这个链接。

OSR:OSR 即 On-Stack Replacement(栈上替换),OSR是一种在运行时替换正在运行的函数/方法的栈帧的技术。这个是为了分层编译引入的,因为有时候我们运行的方法是一个while(ture)这种死循环方法,分层编译找不到时机能把低优化的代码替换成高优化的代码,所以引入了栈上替换,在方法运行中就可以替换成更优的方法。链接1、链接2。

OSR:OSA 即 Object Stack Allocation (对象栈上分配),在.NET中的引用对象默认是分配在堆上的,回收时需要垃圾回收器介入,而且分配对象时必须初始化内存(全部初始化为0),如果对象的生命周期可控,那么可以将它分配在栈上。这样做的好处就是能降低GC压力(方法栈结束,对象自动释放了),提升性能(可以进行标量替换,访问更快)。链接1。

每个组的命名和参数如下所示。

项目备注
Normal正常发布,对照组
Normal-WksGC正常方式,使用WorkStationGC
Normal_PGO正常发布,使用PGO
Normal_PGO_OSR正常发布,使用OSR
Normal_PGO_OSR_OSA正常发布,使用PGO+OSR+OSA
SingleFilePublish普通单文件发布
SingleFilePublish-SelfContained包含运行时单文件发布
SingleFilePublish-SelfContained-Trim包含运行时单文件发布+剪裁程序集
SingleFilePublish-SelfContained-Compress包含运行时单文件发布+压缩程序集
SingleFilePublish-SelfContained-Trim-Compress包含运行时单文件发布+剪裁+压缩程序集
AOT-SizeAOT编译,使用Size模式
AOT-SpeedAOT编译,使用Speed模式

下方的小标题是评测项的方式和评测的结果,每个项我们都会跑5次,最后取平均值。

发布相关

在本节中,Normal那几项编译参数都是一样的,所以结果几乎没有差别,无需过多关注,忽略就好。

发布耗时

发布耗时这个参数,是记录了dotnet publish的耗时,其中会清理/bin、/obj等文件夹,避免缓存带来的影响。
d1747bed5ec3885d9cf7e0d94bc279db.png

可以看到单文件发布和AOT发布还是比较吃性能的,特别是AOT场景下简单的ASPNET Core项目的发布时间就到了接近30秒和一些Rust、C++项目编译速度有的一拼了,要是更大的项目估计会更长。不过正常发布还是很快的,不会一两秒内都能完成。

目录大小

目录大小是直接统计发布以后的目录所占用的硬盘空间,注意:Normal发布都计算了67.5MB的.NET Runtime占用的空间
033319ed250bc4fabf266dd7b7118ddc.png

为什么AOT的目录大小会这么大呢?主要就是上文中提到的用于调试程序的pdb文件变的很大,这是因为AOT以后程序本身缺失很多用于调试的数据,只能存放在pdb文件中,不过这个对于使用没有什么影响,发布时也可以通过-p:DebugType=false-p:DebugSymbols=false参数让它不生成pdb文件。

程序大小

程序大小统计只发布文件中需要运行程序的大小,这个是和分发项目息息相关的,越小的程序体积,就越容易分发。注意:Normal发布都计算了67.5MB的.NET Runtime占用的空间
6e6561138614960eb4f4d4c65d52889a.png

如果目标平台已经预装了.NET Runtime,其实正常发布的效率是最高的,只有一百多KB的大小;次之就是单文件发布+自包含运行时+裁剪+压缩,大小只有20来MB,也比较利于分发。AOT的表现也同样亮眼。

程序运行相关

程序运行相关一共有三个指标,分别为启动耗时、应用启动耗时和内存占用,这里没有设置CPU相关的指标,是因为启动程序CPU基本都是0没有太大的参考意义。下方流程图展示了这几个指标的采集时间。
8ef5d7efadfc8c2ebab43614f1db0c60.png

启动耗时

程序的启动耗时结果如下所示。
3657291f31326fdbb8e518d2204aab56.png
我们可以看到两个极值,最大的单文件+自包含运行时+压缩启动耗时到170ms,因为没有剪裁程序集,需要解压缩的依赖很大,所以启动耗时会比较长一点。最小的AOT-Speed模式只需要16.8ms就能启动程序,看来没有了JIT编译和程序集加载的过程,果然快很多。

应用启动耗时

73cf8181c3150a140b4afb3e53e7f169.png

应用启动耗时和程序启动耗时排列基本一致,像单文件+自包含运行时+压缩启动耗时需要0.5s+才能启动程序,而AOT模式只需要70ms,中间差了七八倍。不过正常发布启动速度也很快,只需要200ms不到的时间。

内存占用

d293b90f7fee5f90c1ba43a280c1f40b.png
内存占用各个方式差别不大,但是也提醒到了我们,如果想让内存占用小一些,那么可以使用WorkstationGC模式。引入动态PGO之类的JIT增强特性以后,相应的会多占用一些内存。

性能压测

机器配置:

CPU:I7 8750H 关闭超线程
RAM:48GB
Client:设置CPU亲和性,绑定3个核心
Server:设置CPU亲和性,绑定2个核心

由于笔者机器配置有限,没有做ClientServer的环境隔离,只做了简单的CPU绑核,所以的出来的数据仅供参考。

压测QPS

d1128535a0cde728dfb0ab00bd4ef9e6.png
可以看到其实各个方式差别不是很大,都取得了4.7Wqps以上的成绩,最大和最小在4%以内。由于这是IO密集型任务,JIT、PGO的优势没有体现出来,后面可以试试一些计算密集型的任务,或者直接看hez的博客,上文介绍PGO中有链接。

单次请求耗时

下图中在条形图内较大的是单次请求耗时(MAX),在条形图外的0.x的数据是单次请求耗时(AVG)。单位是ms.
eea699feb9f451d3160033e4d4830833.png
我们发现平均耗时基本在0.3ms左右,AOT和单文件+自包含运行时+剪裁+压缩的表现很亮眼,只有370ms左右。

压测内存占用

下图中深色代表内存占用(MAX)而浅色代表内存占用(AVG),单位是MB.
f1c2ab0b0cbd0a89a337dc42e11fc648.png
可以看到除了AOT以外的方式,内存占用是大差不差的,4.7Wqps下只需要25MB左右的内存其实很不错了,近似的数字可以理解为误差;另外开启了JIT特性以后,就需要占用更多的内存。AOT的话内存占用就比较多了,可能GC算法在AOT环境下的优化还不够。

压测CPU占用

下图中深色代表CPU占用(MAX)而浅色代表CPU占用(AVG)。单位为百分比;1个CPU核心是100%,如果占用5个CPU核心那么就是500%
c4338041b7c0643c213a8668347c7f5f.png
基本上都没有啥区别,但是AOT方式占用率就小了很多,毕竟没有了JIT这个步骤。

总结

这个结论也就是图一乐,毕竟目前AOT还没有正式发布(已经合并主分支.NET7会正式发布),还有很多值得优化的地方。另外像OSR、OSA这些特性也还没有完全定下来,下面是一些和对照组比较的百分比数据,原始数据和测试代码见Github。后续.NET7正式发布了,再跑一下试试。
eec50e7f86487de8ea20a1ce500ee9f7.png
abe37b38682d2ac0b863c3ac7c93ed7f.png

回答开始提到的问题,总得来说AOT对缩小软件大小,提升应用启动速度有着很大的作用,但是目前需要很长的发布时间和占用更多的内存
另外PGO等一些JIT特性需要比正常情况下占用更多的内存,其性能的优势在这个IO密集的场景没有很好的表现出来。

最后在多说几句,我一直觉得C#是一个很好的语言,.NET是一个很好的平台。从2002年一路走来,今年是.NET的第20个年头,各种新特性相继加入,性能也已经站在了第一梯队,希望以后能有更多的发展吧。
PS:在前几天更新的Benchmarks Game数据里面,C# .NET已经是带JIT语言里面跑的最快的了,仅次于C、C++、Rust等编译型语言,详情可见链接1、链接2。
6ba5d50f408ab6734a6f1149cf3dc949.png

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

无法识别的属性“targetFramework”。请注意属性名称区分大小写。

asp.net部署出错(targetFramework无法识别) 今天尝试着部署了一个基于Framework4.0的web项目,途中发生了一点小小的意外。报的错误是Web.Config配置文件中的 targetFramework属性无法识别。后来查了一下发现在站点中部署的Web使用的是基于.Net Framework2.0的Applic…

.NET点滴:说说Middleware构造中获取不到Scoped服务的问题

今天小桂问我:“为什么中间件的构造函数里不能使用scope的生命周期类型啊?”,那就用实例来得到答案吧,先看小桂说的情况,是报错的:var builder WebApplication.CreateBuilder(args);builder.Services.AddS…

Java游戏有易筋经_当年武侠游戏中绝世秘籍易筋经!重置游戏几十次,玩家才终于找到?...

原标题:当年武侠游戏中绝世秘籍易筋经!重置游戏几十次,玩家才终于找到?在金庸的笔下产生过诸多绝世武学,其中最常被人提及的莫过于少林绝学易筋经。这部由达摩祖师于嵩山少林寺面壁9年留下的武学经书,亦是武…

微软是如何解决 PC 端程序多开问题的——内部实现

前言上次,我们通过《引用 Microsoft.VisualBasic 解决程序多开的问题》。虽然它非常简单,但是仅适用于 WinForm 应用程序,而且还需要引用不常用的Microsoft.VisualBasic类库。因此,我们决定深挖一下,看看具体是如何实现…

svn之回滚到指定版本

1 问题 我们用svn下载了最新的代码,但是我们需要回退到制定的版本 2 操作 我们先svn showlog 找到对应的版本号 如果用的是svn图形界面客户端 我们点击关联svn项目的鼠标右键,然后有个update to reversion 写上相应的版本即可

【C语言简单说】十二:逻辑运算符

**总觉得今天更两节就好了。 ( ̄. ̄) ** 困。。。( ̄. ̄) \ 今天我们来加深if的使用,我们来讲解运算符&&和||,一个叫做 与&& 一个叫做 或&&;我们先来说 && 我们…

正则匹配字符串无匹配不到_实现简单正则表达式匹配

这是facebook的面试题实现包含以下特殊字符的正则表达匹配“.” 匹配任意一个字符“*” 匹配任意多个前面的字符例如给定正则表达“ra.”和字符串“ray”你的函数应该返回true,但是仍用这个正则匹配另一个字符串“raymond”应当返回false给定正则表达“.*at”和字符…

【C语言简单说】十三:逻辑运算符||

(﹏)~ 更完睡觉。 这一节我们来说说逻辑或||,其实很简单的,既然你们理解了第一个逻辑与,那么逻辑或就没什么难度了。 我们说过逻辑与就像我们的并列关系,例如我们吃了苹果和李子。我们现在的逻辑或呢&a…

HQ-day2 C#语言基础

今天主要学习了C#的一些语言基础。 主要有:输入、输出。 一丶输入Console.readline(); 输入语句 输出console.write(); 输出语句 Console.writeline(); 输出语句自动换行 注释 CtrlK 然后 按C ,注释选中部分 CtrlK 然后 按U, 取消注释 二、…

【C语言简单说】十三:变量的生命周期

这次我们就来说说生命周期的问题。其实声明周期的意思就是他这个变量的作用范围,啥是作用范围?唔。。。看我举例子吧,意会,意会。。。 首先,我想问一下你们,如果你们校长叫做小明,你们班也有一…

ASP.NET 6 中间件系列 - 条件中间件

这篇文章是 ASP.NET 6 中间件系列文章的第 4 部分。到目前为止,我们已经介绍了 ASP.NET 6 中间件的基础知识,展示了如何创建自定义中间件类,并讨论了中间件执行顺序的重要性。在本系列的最后一部分中,我们将展示在管道中有条件地执…

【C语言简单说】十四:for循环

说到了循环,我们的内容也就差不多了,此内容并不深入讲解太多东西,大家学习完,如果要深入的话可以查看相关书籍,详细大家学习完后可能会觉得看书上说的可能就看懂了。 可能会有人说为什么不说进制?或者说位…

使用RxJava从多个数据源获取数据

试想,需要一些动态数据的时候,只要每次都请求网络就可以了。但是,更有效率的做法是,把联网得到的数据,缓存到磁盘或内存。 具体的说,计划如下: 偶尔的联网操作,只为获取最新数据。 …

centos 零碎学习小记 2.

1.具体实验(让网卡ethX 里设置的DNS,不影响DNS配置文件 /etc/resolv.conf)1.装完系统的人都想把机器连接外网,都会先看看自己网卡。那么我们用神马呢?简单实用 #ifconfig -a 看看自己网卡信息2.网卡看了开始设置您的网络吧&#…

AspNetCore配置多环境log4net配置文件

前言在之前的文章中有讲到AspNetCore多环境配置文件的应用,我们根据自己多种环境分别配置多个appsettings.$EnvironmentName.json文件。在实际的开发中我们可能会遇到不只一个配置文件,如当我们使用log4net日志库时,喜欢使用单独的log4net.co…

tcp udp区别优缺点_一文搞懂TCP与UDP的区别

一、TCP协议:位于传输层, 提供可靠的字节流服务。所谓的字节流服务(Byte Stream Service) 是指, 为了方便传输, 将大块数据分割成以报文段(segment) 为单位的数据包进行管理。而可靠的传输服务是指, 能够把数据准确可靠…

《企业级ios应用开发实战》一导读

前 言 为什么写这本书随着我国3G网络和移动互联网的兴起,许多传统的企业应用正在从桌面向移动终端扩展,移动办公、移动营销、移动作业等需求日渐强烈。有迹象表明,传统的互联网正在向移动互联网发展。根据摩根士丹利发布的全球互联网发展趋势…

为什么要选择Hibernate

2019独角兽企业重金招聘Python工程师标准>>> 见: http://onecan.iteye.com/blog/1387920 转载于:https://my.oschina.net/sniperLi/blog/416396

苹果手机5s无需越狱免流_苹果越狱手机端自签名插件

unc0ver越狱官网: https://github.com/pwn20wndstuff/Undecimus (此网站ipa需要签名)已经签名网站: https://jailbreaks.fun/支持iOS11.0~12.1.3~12.4 支持 iPhone5S/SE/6/6P/6S/6SP…

【C语言简单说】十五:while循环

上一节说了for循环&#xff0c;那么我们说一下while循环&#xff1b;其实都是循环&#xff0c;就好比肯德基和徳啃鸡一样&#xff0c;卖的都是鸡~ ㄟ(▔&#xff3e;▔ㄟ) (╯▔&#xff3e;▔)╯ 上代码&#xff1a; #include<stdio.h> #include<stdlib.h> int…