.NET 5.0のコンソールアプリケーション簡易性能検証

# はじめに
Delphi 5で作成されていたインターフェイス用のコンソールアプリケーションがあり、C# に作り変えました。
ただ社内検証では問題が発生しなかったが、ユーザー環境では1回ごとの起動処理が3秒ほどかかるとして導入が見送りになりました。その後にいろいろ調査して、固定IPアドレスとプロクシの自動検出が原因でした。
[【.NET】HttpClientの初回が遅いのはプロクシの自動検出が原因](https://qiita.com/yaju/items/771bdf1eddcd2f35bf06)

起動処理が3秒ほどかかる問題は解決したものの、導入は1年以上経っても何故か進みません。Delphi 5版は長年使用していて安定しているってのもありますが、Delphi 5版 と C#版 のWindows 7での性能検証で差が出ているのが一つの要因になっているのかも知れません。ただ作業者の運用を考えたら問題ないレベルではあると思っているのですが説得できる材料が乏しいのか、他の言語で作成した場合とDelphiの最新版に変更した場合の開発工数の依頼がきてました。

【2021/01/23追記】
コメントを頂いた、ReadyToRunを有効にした結果を追加しました。
[.NET Core 3.0 で有効化される Tiered Compilation と ReadyToRun について](https://blog.shibayan.jp/entry/20190912/1568291738)

【2021/03/09追記】
④Windows 7 PCを追加しました。
結果を見ると、作業者の運用を考えたら問題ないレベルだったというのは勘違いで、ユーザーが多く使用しているPCのスペックを考慮していなかった。開発用PCまたはユーザー検証で提供されたPCとの性能に違いがあった。

【2021/06/01追記】
⑤Raspberry Pi ４を追加しました。
Linux-Armでは、ReadyToRunのオプションは選択できなかった。

# 仕様
C++ のアプリケーション側から、CreateProcessでexe形式のインターフェイスを呼ぶようになっています。
インターフェイス側では呼び出し側から渡されたパラメーターを解析して、Webサーバー(IIS 10.0 + ASP.NET + Classic ASP)で処理（DBアクセス等）をして、結果をテキストファイル(Result.txt)に返すようになっています。

# .NET 5.0に移植
2021年4月頃に、Raspberry Piで動作させる依頼がくるかも知れないということで、.NET Framework 4.x から .NET 5.0 に移植してみました。Raspberry Piの実機がないので動作するかはまだ確認していません。

「'shift_jis' is not a supported encoding name.」のエラーになった以外は、問題なく移植できました。
[.NET CoreでShift-JISを取り扱う方法](https://blog.hitsujin.jp/entry/2019/01/27/200055)

# 性能検証
## 環境
* ① Windows 10 IoT 64bit Intel Core(TM) i3-8145U CPU 2.10GHz 2.30GHz メモリ 4.0GB
* ② Windows 10 Pro 64bit Intel Core(TM) i7-8565U CPU 1.80GHz 2.00GHz メモリ 8.0GB
* ③ Windows Server 2016  Intel Xeon CPU 3 E3-1220 v6 3.00GHz 3.00GHz メモリ 16.0GB
* ④ Windows 7 Pro 32bit Intel Celeron(R) CPU G1840 @2.8GHz 2.80 GHz メモリ 2.0GB
* ⑤ Raspberry Pi ４(B03114) ARMv7 Processor rev 3 メモリ 2.0GB

①は工場などの制御端末や組み込み端末用で「機能更新プログラム」が提供されず10年間機能固定でご利用するもの。
②は会社で支給された自分が現在使用しているもの。
③はサーバーです。インターフェイスはクライアントPC側で動かすものなので参考用です。
④はユーザー側の工場で多く使用されているもの。

インターフェイスアプリケーションは、Windows 7 PCでも動作するように 32bitアプリケーション(x86)になっています。

## 検証方法
C++でCreateProcessでexe形式のインターフェイスアプリケーションを呼び出し、処理時間のログを出力するだけのドライバアプリケーションを作成して4回実行します。ただ初回のみ時間がかかる(コールドスタートアップ)ため除外、実質3回の平均を取ります。

* Delphi
* .NET Framework 4.x
* .NET 5.0
* .NET 5.0 自己完結型(単一ファイル)
* .NET 5.0 自己完結型(単一ファイル) ReadyToRun(R2R)
* .NET 5.0 自己完結型

※自己完結型は、ランタイム込みで配布先に.NET 5.0がインストールされていなくても動く形式です。
※ReadyToRunは、コンパイル時にある程度事前コンパイルして発行したバイナリに含める機能。
※自己完結型(単一ファイル)でも、exeとpdbファイル以外に4つのファイル(clrcompression.dll,clrjit.dll,coreclr.dll,mscordaccore.dll)を一緒に配置する。

### exeファイルのみのサイズ
|プラットフォーム|サイズ|内容|
|:---|---:|:---|
|Delphi|492 KB|ランタイム依存型|
|.NET 4.x|42 KB|フレームワーク依存型|
|.NET 5.0|112 KB|フレームワーク依存型|
|.NET 5.0 Self(Single)|20.8 MB|自己完結型(単一ファイル)|
|.NET 5.0 R2R(Single)|38.7 MB|自己完結型(単一ファイル) ReadyToRun(R2R)|
|.NET 5.0 Self|XX MB|自己完結型

## 検証実施
**平均は初回のみ除外**

① Windows 10 IoT 64bit Intel Core(TM) i3-8145U CPU 2.10GHz 2.30GHz メモリ 4.0GB
【2021/01/22 計測結果】

|プラットフォーム|初回|2回目|3回目|4回目|平均ms|
|:---|---:|---:|---:|---:|---:|
|Delph|1199|220|215|218|217.7|
|.NET 4.x|3064|266|254|256|258.7|
|.NET 5.0|1638|744|748|749|747|
|.NET 5.0 Self(Single)|3886|376|381|378|378.3|
|.NET 5.0 R2R(Single)|3168|1346|1187|1249|1260.7|

【2020/12/25 計測結果】

|プラットフォーム|初回|2回目|3回目|4回目|平均ms|
|:---|---:|---:|---:|---:|---:|
|Delphi|2188|636|652|601|629.7|
|.NET 4.x|8618|785|629|690|701.3|
|.NET 5.0|4497|874|817|841|844|
|.NET 5.0 Self(Single)|3301|1253|1218|1237|1236|

② Windows 10 Pro 64bit Intel Core(TM) i7-8565U CPU 1.80GHz 2.00GHz メモリ 8.0GB
【2021/01/22 計測結果】

|プラットフォーム|初回|2回目|3回目|4回目|平均ms|
|:---|---:|---:|---:|---:|---:|
|Delph|645|489|388|312|396.3|
|.NET 4.x|1242|369|387|362|372.7|
|.NET 5.0|1865|606|609|602|605.7|
|.NET 5.0 Self(Single)|1254|1163|1159|1185|1169|
|.NET 5.0 R2R(Single)|3529|1334|1275|1300|1303|

【2020/12/25 計測結果】

|プラットフォーム|初回|2回目|3回目|4回目|平均ms|
|:---|---:|---:|---:|---:|---:|
|Delphi|495|299|292|270|287|
|.NET 4.x|2704|295|358|355|336|
|.NET 5.0|1496|559|637|578|591.3|
|.NET 5.0 Self(Single)|2560|1787|1763|1817|1789|

③ Windows Server 2016  Intel Xeon CPU 3 E3-1220 v6 3.00GHz 3.00GHz メモリ 16.0GB
【2021/01/22 計測結果】

|プラットフォーム|初回|2回目|3回目|4回目|平均ms|
|:---|---:|---:|---:|---:|---:|
|Delph|153|144|148|150|147.3|
|.NET 4.x|538|153|144|145|147.3|
|.NET 5.0|2247|217|261|278|252|
|.NET 5.0 Self(Single)|809|475|502|473|483.3|
|.NET 5.0 R2R(Single)|1111|263|248|254|255|

【2020/12/25 計測結果】

|プラットフォーム|初回|2回目|3回目|4回目|平均ms|
|:---|---:|---:|---:|---:|---:|
|Delphi|349|116|112|119|115.7|
|.NET 4.x|1047|128|116|114|119.3|
|.NET 5.0|429|240|212|234|228.7|
|.NET 5.0 Self(Single)|917|460|432|418|436.7|

④ Windows 7 Pro 32bit Intel Celeron(R) CPU G1840 @2.8GHz 2.80 GHz メモリ 2.0GB
【2021/03/08 計測結果】

|プラットフォーム|初回|2回目|3回目|4回目|平均ms|
|:---|---:|---:|---:|---:|---:|
|Delphi|1031|313|308|317|312.7|
|.NET 4.x|6021|5880|5723|5687|5763.3|
|.NET 5.0|5855|2295|2088|2181|2188|
|.NET 5.0 Self(Single)|2024|2805|2724|2723|2750.7|
|.NET 5.0 R2R(Single)|2570|3899|3860|3899|3886|

⑤ Raspberry Pi ４(B03114) ARMv7 Processor rev 3 メモリ 2.0GB
【2021/05/28 計測結果】

|プラットフォーム|初回|2回目|3回目|4回目|平均ms|
|:---|---:|---:|---:|---:|---:|
|.NET 5.0 Self(Single)|5000|3230|3110|3100|3146.7|
|.NET 5.0 Self|3530|1870|1880|1820|1856.7|

※Linux-Armでは、ReadyToRun(R2R)のオプションが選択できなかった。
※本体にインストールできない運用のため、フレームワーク依存型は検証しなかった。
※USBメモリーに格納した状態で検証している。
※ビルド直後の初回は12秒ぐらいかかっていた。検証結果はビルド直後に実行してからPC再起動の初回。
※計測は「[bash で処理時間を 10 ミリ秒単位で計測する方法](https://luna2-linux.blogspot.com/2011/10/bash-10.html?m=0)」を使用した。

## 結果まとめ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14053/d644d2bf-22c4-5a16-42f6-41af2f59c377.png) ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14053/f102d907-ba9f-e6f9-12e7-f58f55c6ccab.png)

Delphi と .NET Framework 4.x については同等性能です。※Delphi版はネイティブコードです。
.NET 5.0 に関しては、パフォーマンスが向上したと聞いていたが、この検証においては100ms〜200ms遅いです。
.NET 5.0 自己完結型は、exeサイズが大きいだけあって今回のように随時呼ばれるのには向いてないです。Windows ServerくらいPC性能がよければ使用しても問題ないでしょう。
.NET 5.0 ReadyToRunは、期待していたほど初回速度も実行速度も良くなかったものの、Windows ServerくらいPC性能がよければ実行速度は.NET 5.0 SDKより速くなるので、将来性が高いです。

グラフでみると差があるように見えますが、ミリ秒単位なので実行するたびにブレが出てきてしまいます。それでも実行してみて1秒超えると遅いって感じますね。

【2021/03/09追記】
Windows 7といってもメモリ 2GBと貧弱ではあるのですが、ユーザー側のスペックはこんなものです。工場で多く使われているとコスト面もあり壊れたら買い換えるというスタンスです。
メモリ 2GBでは.NET Framework 4.x だと遅く .NET 5 の方がまだマシな結果となりました。Delphi と比較したらどちらも使用する上では遅すぎます。最低でもメモリ 4GBないと辛いです。 

【2021/06/01追記】
Raspberry Pi ４は貧弱なのでどうなるのかと思ったが、2秒以内が出てたのでWindows 7と比べたら良かったかも。
単一ファイルにすると展開に時間がかかるので、単一ファイルにしない方が速かった。
他のも時間があれば単一ファイルにしないパターンを追加して検証し直したい。

# 最後に
.NET 5.0 に関しては、パフォーマンスが向上したと聞いていたので少し残念な結果です。でも、Raspberry Piはコンパイルするだけで動作するようになるのだから、開発効率はいいです。とはいえ、Raspberry Piで試してないので遅くて使い物にならなかったら、C++あたりで作り直すことになるのかも。

ユーザー側はWindows 7のPCがまだ大半なので、本来はWindows 7の性能検証結果を入れたかったのですが、~~環境準備が整わなかったので今後追加しておきます。~~ 2021/03/09に追記しました。~~また、Raspberry Piも検証できる環境ができたときには反映しておきます。~~2021/06/01にRaspberry Piを追記しました。