AOT编译和单文件发布对程序性能有何显著差异?
摘要:## 前言 这里先和大家介绍一下.NET一些发布的历史,以前的.NET框架原生并不支持最终编译结果的单文件发布(需要依赖第三方工具),我这里新建了一个简单的ASP.NET Core项目,发布以后的目录就会像下图这样,里面包含很多`*.dll
前言
这里先和大家介绍一下.NET一些发布的历史,以前的.NET框架原生并不支持最终编译结果的单文件发布(需要依赖第三方工具),我这里新建了一个简单的ASP.NET Core项目,发布以后的目录就会像下图这样,里面包含很多*.dll文件和其它各类的文件。
在.NET Core 2.1时代,引入了单文件发布的功能,只需要在发布命令上,增加-p:PublishSingleFile=true参数就可以使用,从这以后就无需发布的文件夹就再也没有那么多的文件,只有一个*.exe文件和对应的配置文件和用于调试*.pdb的文件,如下所示:
不过此时的.NET还是需要安装一个大小为50~130MB左右的.NET Runtime才能运行,这个其实不利于在客户端场景下程序的分发,大家应该能回忆起在安装一些软件之前,必须安装.NET Framework的场景。
在单文件发布推出的同时,也可以通过--self-contained true的参数,将运行时也包含在发布文件内,这样的话就无需在目标机器上再安装.NET Runtime。不过由于它自带运行时,整个发布文件夹的大小就变得很大了,可以说比安装.NET Runtime还要大一些(足足82.4MB)。
程序本质上也就是文件,我们也可以通过压缩程序的方式,让它的大小变小,只需要加上-p:EnableCompressionInSingleFile=true参数。就可以将80MB的程序压缩至44MB左右。
单文件发布体积大的原因就是包括了所有运行可能用到的依赖,不过有很多依赖是我们程序中用不到的,所以发布的时候可以加-p:PublishTrimmed=true参数,发布的时候移除掉没有使用的依赖,这样体积就可以降低很多(从44MB到35MB)。
当然,移除没有使用的依赖和压缩是可以同时使用的,这样发布以后,体积就可以变得更小了,只需要20MB左右。
此时.NET运行还是需要自带运行时,在运行.NET程序的时候需要JIT来参与,这样的话在应用启动时需要一定的时间让JIT将MSIL编译到对应平台机器码,随后.NET推出了预览版的Native-AOT,可以在编译时直接将代码编译成对应平台的机器码,以加快启动速度;另外由于不需要自带运行时,它整个的体积大小也变得很小。
用于调试的pdb文件就会变得很大,不过真实发布的话也用不到这个文件,可以舍弃。AOT以后的大小也就20MB左右。不过AOT也不是银弹,由于没有了JIT,很多编译时优化就不能做了,Java的GraalVm发布的时候就有一张五边形图,充分的说明了JIT和AOT之间的取舍。
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。
