.NET Core のパフォーマンス
こんにちは、みんな! この記事は、私と同僚がさまざまなプロジェクトに取り組む際に長年使用してきたベスト プラクティスをまとめたものです。
計算が実行されたマシンに関する情報:BenchmarkDotNet=v0.11.5、OS=Windows 10.0.18362
Intel Core i5-8250U CPU 1.60GHz (Kaby Lake R)、1 CPU、8 論理コアと 4 物理コア
.NETコアSDK=3.0.100
[ホスト]: .NET Core 2.2.7 (CoreCLR 4.6.28008.02、CoreFX 4.6.28008.03)、64bit RyuJIT
コア: .NET Core 2.2.7 (CoreCLR 4.6.28008.02、CoreFX 4.6.28008.03)、64 ビット RyuJIT
[ホスト]: .NET Core 3.0.0 (CoreCLR 4.700.19.46205、CoreFX 4.700.19.46214)、64bit RyuJIT
コア: .NET Core 3.0.0 (CoreCLR 4.700.19.46205、CoreFX 4.700.19.46214)、64 ビット RyuJIT
ジョブ=コア ランタイム=コア
ToList と ToArray およびサイクル
.NET Core 3.0 のリリースに合わせてこの情報を準備するつもりでしたが、やられてしまいました。他人の栄光を盗んだり、他人の情報をコピーしたりしたくないので、指摘だけさせていただきます。
私自身の代わりに、私の測定結果と結果を紹介したいと思います。「C++ スタイル」のループ記述の愛好家のために、それらに逆ループを追加しました。
コード:
public class Bench
{
private List<int> _list;
private int[] _array;
[Params(100000, 10000000)] public int N;
[GlobalSetup]
public void Setup()
{
const int MIN = 1;
const int MAX = 10;
Random random = new Random();
_list = Enumerable.Repeat(0, N).Select(i => random.Next(MIN, MAX)).ToList();
_array = _list.ToArray();
}
[Benchmark]
public int ForList()
{
int total = 0;
for (int i = 0; i < _list.Count; i++)
{
total += _list[i];
}
return total;
}
[Benchmark]
public int ForListFromEnd()
{
int total = 0;t
for (int i = _list.Count-1; i > 0; i--)
{
total += _list[i];
}
return total;
}
[Benchmark]
public int ForeachList()
{
int total = 0;
foreach (int i in _list)
{
total += i;
}
return total;
}
[Benchmark]
public int ForeachArray()
{
int total = 0;
foreach (int i in _array)
{
total += i;
}
return total;
}
[Benchmark]
public int ForArray()
{
int total = 0;
for (int i = 0; i < _array.Length; i++)
{
total += _array[i];
}
return total;
}
[Benchmark]
public int ForArrayFromEnd()
{
int total = 0;
for (int i = _array.Length-1; i > 0; i--)
{
total += _array[i];
}
return total;
}
}
.NET Core 2.2 と 3.0 のパフォーマンス速度はほぼ同じです。 .NET Core 3.0 で得られたものは次のとおりです。
Array コレクションの反復処理は、内部の最適化と明示的なコレクション サイズの割り当てにより高速であると結論付けることができます。 List コレクションには独自の利点があるため、必要な計算に応じて適切なコレクションを使用する必要があることも覚えておく価値があります。 ループを操作するロジックを作成する場合でも、これは通常のループであり、ループの最適化の対象となる可能性があることを忘れないでください。 かなり前に habr で記事が公開されました。
投げる
XNUMX 年前、私はある会社でレガシー プロジェクトに取り組んでいました。そのプロジェクトでは、try-catch-throw 構造を通じてフィールド検証を処理するのが通常でした。 これはプロジェクトにとって不健全なビジネス ロジックであることはすでに理解していたので、可能な限りそのような設計は使用しないようにしました。 しかし、このような構造でエラーを処理するアプローチがなぜ悪いのかを考えてみましょう。 XNUMX つのアプローチを比較するための小さなコードを作成し、各オプションのベンチマークを作成しました。
コード:
public bool ContainsHash()
{
bool result = false;
foreach (var file in _files)
{
var extension = Path.GetExtension(file);
if (_hash.Contains(extension))
result = true;
}
return result;
}
public bool ContainsHashTryCatch()
{
bool result = false;
try
{
foreach (var file in _files)
{
var extension = Path.GetExtension(file);
if (_hash.Contains(extension))
result = true;
}
if(!result)
throw new Exception("false");
}
catch (Exception e)
{
result = false;
}
return result;
}
.NET Core 3.0 と Core 2.2 の結果も同様の結果になります (.NET Core 3.0)。
Try catch を使用すると、コードが理解しにくくなり、プログラムの実行時間が長くなります。 ただし、この構造が必要な場合は、エラーの処理が期待されていないコード行を挿入しないでください。これにより、コードが理解しやすくなります。 実際、システムに負荷をかけるのは例外の処理ではなく、 throw new Exception 構造を介してエラー自体をスローすることです。
例外のスローは、必要な形式でエラーを収集するクラスよりも遅くなります。 フォームまたは一部のデータを処理していて、エラーの内容が明確にわかっている場合は、それを処理しないのはなぜでしょうか。
この状況が例外的でない場合は、 throw new Exception() コンストラクトを作成しないでください。 例外の処理とスローは非常にコストがかかります。
ToLower、ToLowerInvariant、ToUpper、ToUpperInvariant
.NET プラットフォームでの 5 年間の作業経験を通じて、文字列マッチングを使用する多くのプロジェクトに出会ってきました。 次の図も見ました。XNUMX つのエンタープライズ ソリューションに多くのプロジェクトがあり、それぞれが異なる方法で文字列比較を実行していました。 しかし、何を使用し、どのように統一すればよいのでしょうか? Richter 著『CLR via C#』という本で、ToUpperInvariant() メソッドが ToLowerInvariant() よりも高速であるという情報を読みました。
本からの抜粋:
もちろん、私はそれを信じず、.NET Framework でいくつかのテストを実行することにしました。その結果は私に衝撃を与えました。パフォーマンスが 15% 以上向上しました。 そして、翌朝出勤すると、これらの測定値を上司に見せ、ソース コードへのアクセスを許可しました。 その後、2 プロジェクトのうち 14 プロジェクトが新しい測定に対応するために変更されました。これら XNUMX つのプロジェクトが巨大な Excel テーブルを処理するために存在していたことを考慮すると、その結果は製品にとって十分以上に重要でした。
また、.NET Core のさまざまなバージョンの測定結果も提示するので、各人が最適なソリューションを選択できるようになります。 私が働いている会社では、文字列の比較に ToUpper() を使用していることを付け加えておきたいと思います。
コード:
public const string defaultString = "VXTDuob5YhummuDq1PPXOHE4PbrRjYfBjcHdFs8UcKSAHOCGievbUItWhU3ovCmRALgdZUG1CB0sQ4iMj8Z1ZfkML2owvfkOKxBCoFUAN4VLd4I8ietmlsS5PtdQEn6zEgy1uCVZXiXuubd0xM5ONVZBqDu6nOVq1GQloEjeRN8jXrj0MVUexB9aIECs7caKGddpuut3";
[Benchmark]
public bool ToLower()
{
return defaultString.ToLower() == defaultString.ToLower();
}
[Benchmark]
public bool ToLowerInvariant()
{
return defaultString.ToLowerInvariant() == defaultString.ToLowerInvariant();
}
[Benchmark]
public bool ToUpper()
{
return defaultString.ToUpper() == defaultString.ToUpper();
}
[Benchmark]
public bool ToUpperInvariant()
{
return defaultString.ToUpperInvariant() == defaultString.ToUpperInvariant();
}
.NET Core 3.0 では、これらの各メソッドの増加量は ~x2 であり、実装間のバランスが保たれます。
階層コンパイル
前回の記事でこの機能について簡単に説明しましたが、言葉を修正して補足したいと思います。 マルチレベル コンパイルはソリューションの起動時間を短縮しますが、コードの一部がバックグラウンドでより最適化されたバージョンにコンパイルされることを犠牲にし、これによりわずかなオーバーヘッドが発生する可能性があります。 NET Core 3.0 の登場により、層コンパイルが有効になっているプロジェクトのビルド時間が短縮され、このテクノロジに関連するバグが修正されました。 以前は、このテクノロジにより、ASP.NET Core の最初のリクエストでエラーが発生し、マルチレベル コンパイル モードでの最初のビルド中にフリーズが発生していました。 現在、.NET Core 3.0 ではデフォルトで有効になっていますが、必要に応じて無効にすることができます。 あなたがチームリーダー、シニア、中間の立場にいる場合、または部門の責任者である場合は、迅速なプロジェクト開発がチームの価値を高め、このテクノロジーにより両方の開発者の時間を節約できることを理解する必要があります。そしてプロジェクトそのものの時間。
.NETのレベルアップ
.NET Framework / .NET Core のバージョンをアップグレードします。 多くの場合、新しいバージョンごとにパフォーマンスが向上し、新しい機能が追加されます。
しかし、具体的にはどのようなメリットがあるのでしょうか? それらのいくつかを見てみましょう:
- .NET Core 3.0 では、.NET Core アプリケーションの起動時間を短縮する R2R イメージが導入されました。
- バージョン 2.2 では、Tier Compilation が登場しました。これにより、プログラマーはプロジェクトの立ち上げに費やす時間が短縮されます。
- 新しい .NET 標準のサポート。
- 新しいバージョンのプログラミング言語のサポート。
- 最適化。新しいバージョンごとに、基本ライブラリ Collection/Struct/Stream/String/Regex などの最適化がさらに向上します。 .NET Framework から .NET Core に移行する場合は、すぐに使用できるパフォーマンスが大幅に向上します。 例として、.NET Core 3.0 に追加されたいくつかの最適化へのリンクを添付します。
https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-core-3-0/
まとめ
コードを記述するときは、プロジェクトのさまざまな側面に注意を払い、プログラミング言語とプラットフォームの機能を使用して最良の結果を達成することが重要です。 .NET の最適化に関する知識を共有していただければ幸いです。
出所: habr.com