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,一经查实,立即删除!

相关文章

均分纸牌(经典贪心)

1 题目描述 有N堆纸牌,编号分别为1,2,…,N。每堆上有若干张,但纸牌总数必为N的倍数。可以在任一堆上取若干张纸牌,然后移动。 移牌规则为:在编号为1堆上取的纸牌,只能移到编号为2的堆上;在编号为N的堆上取的纸牌,只能移到编号为N−1的堆上;其他堆上取的纸牌,可以移…

Some projects cannot be imported because they already exist in the workspace

1、Some projects cannot be imported because they already exist in the workspace2、Some projects were hidden because they exist in the workspace directory有时候eclipse或者myeclipse导入项目的时候会发生上面两个错误,IDE不允许我们导入。具体原因是在ID…

shell变量/环境变量和set/env/export用法_转

转自:shell环境变量以及set,env,export的区别 一.shell环境变量的分类以及set env export的区别: set:显示(设置)shell变量,包括的私有变量以及用户变量。不同类的shell有不同的私有变量 bash,ksh,csh每中shell私有变量都不一样。 env:显示(设置)用户变…

【C语言简单说】十:小结

([]!!) ㄟ(≥◇≤)ㄏ(&#xffe3;ε(#&#xffe3;) ∑( △ |||)︴ 一言不合发表情。。。 这节我们来做一个小菜单&#xff0c;然后判断我们选择的是啥东西&#xff0c;好了上代码&#xff1a; #include<stdio.h> #include<stdlib.h> int main() {int a1;prin…

Android Studio开发基础之动态注册与注销BroadcastReceiver

1、New→Other→BroadcastReceiver package com.example.lhb.startservice;import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.widget.Toast;public class MyReceiver extends BroadcastReceiver {pu…

python每行输出5个数_python打印杨辉三角及输出第m行第k个数

1.打印杨辉三角及输出第m行第k个数1.计算到m行&#xff0c;打印出k项第m行有m项&#xff0c;m是正整数&#xff0c;因此k一定不会大于m&#xff0c;这个需求需要保存m行的数据&#xff0c;那么可以使用一个嵌套结构[[],[],[]]mint(input(行>>>))kint(input(第几个数&g…

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

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

java解析xml生成表格_JAVA读取XML文件并解析 以及 JAVA生成文本文件输出

废话不多说&#xff0c;直接上代码及说明吧&#xff01;package Dao;import java.io.File;import java.util.ArrayList;import java.util.List;import javax.xml.parsers.DocumentBuilder;import javax.xml.parsers.DocumentBuilderFactory;import org.w3c.dom.Document;import…

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

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

模式识别之分类---数学贝叶斯方法

http://blog.sina.com.cn/s/blog_6163bdeb0102ehh1.html http://www.zdh1909.com/html/Verilog/29941.html

给定0~N之间的N个数字(大于等于0,小于N,不重复)进行按小到大排列(不用其它的经典排序算法)

1 问题 比如我们给定0~N之间的N个数字(大于等于0,小于N,并且不重复)进行按小到大排列,比如N是5,我们给定数据{3, 2, 1, 0, 4},我们按照从小到大的排名最后就是{0, 1, 2, 3, 4} 2 思路 我们遍历数组,下标为i,我们把i和a[i]进行对比,如果不想等我们就进行交换a[i]和a[a[i]]的值…

【C语言简单说】十一:switch 补

昨天丧心病狂的更新了十节。我都懵逼了。。。 *~ (&#xffe3;﹏&#xffe3;) ** 今天继续&#xff0c;来说一下。。。那个。。。另外一个分支switch。 直接上代码&#xff0c;学过了if的孩子就会觉得很简单的了&#xff1a; #include<stdio.h> #include<stdlib.h…

retain、strong、weak、assign区别

1. 假设你用malloc分配了一块内存&#xff0c;并且把它的地址赋值给了指针a&#xff0c;后来你希望指针b也共享这块内存&#xff0c;于是你又把a赋值给&#xff08;assign&#xff09;了b。此时a 和b指向同一块内存&#xff0c;请问当a不再需要这块内存&#xff0c;能否直接释放…

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

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

python路径怎么找windows_如何查看 windows 中 Python安装路径

答疑 qq&#xff1a;3081655843公号&#xff1a; 天枢子python课堂windows平台如果python已经安装好了的话&#xff0c;打开命令行窗口&#xff0c;输入python命令回车&#xff0c;能够进入到python的命令行界面。这个时候我们能够确定的就是&#xff0c;python的安装路径一定被…

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

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

C#常用命名空间

MSDN上的C#.NET Framework类库文档目录树&#xff0c;本人觉得有点不得要领&#xff0c;于是参考搜到的结果简单整理如下&#xff1a; 一、基础命名空间 System 处理内建数据、数学计算、随机数的产生、环境变量、垃圾回收器及一些常见的异常和特征. System.Collections 包含了…

svn之回滚到指定版本

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

番茄工作法—《可以量化的管理学》

6.5.4时间t与番茄工作法 内容提要&#xff1a;番茄工作法划分工作和休息时间&#xff0c;将时间划分为小块&#xff0c;有利于提高工作和休息的效率&#xff0c;提高时间的感知和掌控。 番茄工作法是简单易行的时间管理方法&#xff0c;是由弗朗西斯科西里洛于1992年创立的一种…

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

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