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

在.NET Core 2.1 时代,引入了单文件发布的功能,只需要在发布命令上,增加-p:PublishSingleFile=true参数就可以使用,从这以后就无需发布的文件夹就再也没有那么多的文件,只有一个*.exe文件和对应的配置文件和用于调试*.pdb的文件,如下所示:

しかし、現時点では、. NETはまだ実行するために50~130MBの. NETランタイムのサイズをインストールする必要があります。これは実際にはクライアントシナリオでのプログラムの配布には適していません。いくつかのソフトウェアをインストールする前に. 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はプログラムの起動速度を速くすると言われていますが、どのくらい速くなりますか?
2. 評価結果の推移
私はそれを研究する時間を費やすことにしました、週末に上記の問題で私はテストのセットを設計しましたが、もちろん、時間が急いで多くの不正確な場所がありますが、それはちょうど写真の喜びと言うことができます、あなたの指摘と含蓄を見てください。合計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-Size | サイズモードを使用したAOTコンパイル |
| AOT-Speed | AOTコンパイル、スピードモードを使用 |
下の見出しは、項目の評価方法と結果です。各項目を5回実行して平均を取ります。
2.1関連する投稿。
この節では,Normalのコンパイルパラメータはすべて同じであるので,結果にほとんど差がなく,あまり注目せず無視すればよい.
1.1出版の時間
发布耗时这个参数,是记录了dotnet publish的耗时,其中会清理/bin、/obj等文件夹,避免缓存带来的影响。

シングルファイルリリースとAOTリリースはまだ比較的パフォーマンスを食べていることがわかります。特にAOTシナリオでは、単純なASPNET Coreプロジェクトのリリース時間は30 秒近くに達し、いくつかのRust、C++プロジェクトのコンパイル速度は、大規模なプロジェクトの場合は長くなると推定されます。しかし、通常のリリースは非常に速く、1秒か2秒では完了しません。
2.1.2ディレクトリサイズ
ディレクトリサイズは、リリース後のディレクトリが使用するハードディスク容量を直接カウントします。注:Normalリリースでは、67.5 MBの. NET Runtimeが使用する容量を計算します。

为什么 AOT 的目录大小会这么大呢?主要就是上文中提到的用于调试程序的pdb文件变的很大,这是因为 AOT 以后程序本身缺失很多用于调试的数据,只能存放在pdb文件中,不过这个对于使用没有什么影响,发布时也可以通过-p:DebugType=false和-p:DebugSymbols=false参数让它不生成pdb文件。
1.3プログラムのサイズ
プログラムサイズ統計は、配布プロジェクトと密接に関連しているファイル内の実行に必要なプログラムサイズのみを発行し、プログラムサイズが小さいほど配布が容易になります。注:Normalリリースでは、. NET Runtimeの67.5 MBの容量が計算されます。

ターゲットプラットフォームに. NET Runtimeがプリインストールされている場合、通常のリリースの効率は最も高く、サイズはわずか100 KBです。次に、単一ファイルのリリース+自己完結型ランタイム+トリミング+圧縮が続き、サイズはわずか20 MBで、配布に適しています。AOTのパフォーマンスも同様です。
2.2プログラム実行関連の
起動時間、アプリケーション起動時間、メモリ使用時間の3つの指標の合計がありますが、起動プログラムCPUは基本的に0であり、あまり参照する意味がないため、CPU関連の指標は設定されていません。以下のフローチャートは、これらの指標の収集時間を示します。

2.1起動時間
プログラムの起動にかかる時間の結果を以下に示す.

最大の単一ファイル+自己完結型ランタイム+圧縮起動時間は170msに達します。アセンブリがトリミングされておらず、解凍する必要がある依存関係が大きいため、起動時間は少し長くなります。最小のAOT-Speedモードでは、プログラムを起動するのに16.8 msしかかかりません。JITコンパイルとアセンブリロードプロセスがなく、はるかに高速です。
2.2.2アプリケーション開始時間

アプリケーションの起動時間とプログラムの起動時間は基本的に同じであり、単一ファイル+自己完結型ランタイム+圧縮起動時間はプログラムを起動するのに0.5秒以上かかりますが、AOTモードはわずか70msで、中間差は7 ~ 8倍です。しかし、通常のリリースは高速で、200ms未満の時間しか必要ありません。
2.2メモリの使用

メモリフットプリントの方法はあまり変わりませんが、メモリフットプリントを小さくしたい場合はWorkstation GCモードを使用できることを思い出させてくれます。ダイナミックPGOのようなJIT強化機能の導入により、メモリ消費量が増加します。
2.3パフォーマンステスト。
マシンの構成:
CPU I7 8750Hハイパースレッディングオフ
RAM:48GB
Client:CPU親和性を設定し、3コアをバインドする
Server:CPU親和性を設定し、2つのコアをバインド
由于笔者机器配置有限,没有做Client和Server的环境隔离,只做了简单的 CPU 绑核,所以的出来的数据仅供参考。
3.1 QPSの測定

可以看到其实各个方式差别不是很大,都取得了4.7Wqps以上的成绩,最大和最小在 4%以内。由于这是 IO 密集型任务,JIT、PGO 的优势没有体现出来,后面可以试试一些计算密集型的任务,或者直接看 hez 的博客,上文介绍 PGO 中有链接。
2.3.2単一リクエストの時間
下图中在条形图内较大的是单次请求耗时(MAX),在条形图外的0.x的数据是单次请求耗时(AVG)。单位是ms.

我们发现平均耗时基本在0.3ms左右,AOT 和单文件+自包含运行时+剪裁+压缩的表现很亮眼,只有370ms左右。
2.3.3圧測定メモリの使用
下图中深色代表内存占用(MAX)而浅色代表内存占用(AVG),单位是MB.

可以看到除了 AOT 以外的方式,内存占用是大差不差的,4.7Wqps下只需要25MB左右的内存其实很不错了,近似的数字可以理解为误差;另外开启了 JIT 特性以后,就需要占用更多的内存。AOT 的话内存占用就比较多了,可能 GC 算法在 AOT 环境下的优化还不够。
2.3.4圧力測定CPU占有
下图中深色代表CPU占用(MAX)而浅色代表CPU占用(AVG)。单位为百分比;1 个 CPU 核心是100%,如果占用 5 个 CPU 核心那么就是500%。

基本的には違いはありませんが、AOTの占有率ははるかに小さく、結局のところ、JITステップはありません。
3. まとめまとめまとめ
这个结论也就是图一乐,毕竟目前 AOT 还没有正式发布(已经合并主分支.NET7 会正式发布),还有很多值得优化的地方。另外像 OSR、OSA 这些特性也还没有完全定下来,下面是一些和对照组比较的百分比数据,原始数据和测试代码见GitHub。后续.NET7 正式发布了,再跑一下试试。


最初に述べた質問に答えると、一般的にAOTはソフトウェアのサイズを縮小し、アプリケーションの起動速度を向上させる上で大きな役割を果たしますが、現在はリリース時間が長く、より多くのメモリを消費します。
さらに、PGOなどのJIT機能の一部は、通常よりも多くのメモリを必要とし、IO集約的なシナリオではパフォーマンスの利点はあまり発揮されません。
最後に、C#は素晴らしい言語であり、. NETは素晴らしいプラットフォームだといつも感じていました。2002 年からすべての方法で、今年は. NETの20年目であり、様々な新機能が追加され、パフォーマンスも最初の段階に立っており、将来的により多くの開発があることを願っています。
PS:在前几天更新的 Benchmarks Game 数据里面,C# .NET 已经是带 JIT 语言里面跑的最快的了,仅次于 C、C++、Rust 等编译型语言,详情可见链接 1、链接 2。

オリジナル:Incerry
前の記事:単一ファイルリリースがプログラムパフォーマンスに与える影響
原文へのリンク:https//www.cnblogs.com/InCerry/p/Single-File-And-AOT-Publish.html