序文
リフレクションは常にパフォーマンスのボトルネックとなっているため、どの .NET バージョンであっても、リフレクションの最適化は避けられません。主に、割り当てとキャッシュという 2 つの側面での最適化に焦点を当てています。.NET8も例外ではありません。この記事を読んでください。
原文: .NET8 Ultimate Performance Optimization Reflection
概要
たとえば、リフレクションを通じて属性を取得するための GetCustomAttributes の最適化の場合、次の例は
// dotnet run -c Release -f net7.0 --filter "*" --runtimes net7.0 net8.0
public class Tests
{
public object[] GetCustomAttributes() => typeof(C).GetCustomAttributes(typeof(MyAttribute), inherit: true);
[My(Value1 = 1, Value2 = 2)]
class C { }
[AttributeUsage(AttributeTargets.All)]
public class MyAttribute : Attribute
{
public int Value1 { get; set; }
public int Value2 { get; set; }
}
}
.NET7 と .NET8 の明らかな違いは、主に、プロパティの値を設定するために object[1] 配列を割り当てることを回避するように最適化されていることです。
方法 | ランタイム | 平均値 | 比率 | 配布する | 配分比率 |
---|---|---|---|---|---|
カスタム属性の取得 | .NET 7.0 | 1,287.1ns | 1.00 | 296B | 1.00 |
カスタム属性の取得 | .NET 8.0 | 994.0ns | 0.77 | 232B | 0.78 |
その他には、より自由なスパンによるなど、リフレクション スタック上の割り当ての削減が含まれます。Type のジェネリック処理が改善され、結果が Type オブジェクトにキャッシュされるようになった GetGenericTypeDefinition などのさまざまなジェネリック関連メンバーのパフォーマンスが向上しました。
// dotnet run -c Release -f net7.0 --filter "*" --runtimes net7.0 net8.0
public class Tests
{
private readonly Type _type = typeof(List<int>);
public Type GetGenericTypeDefinition() => _type.GetGenericTypeDefinition();
}
.NET7と.NET8は以下の通りです
方法 | ランタイム | 平均値 | 比較する |
---|---|---|---|
GetGenericTypeDefinition | .NET 7.0 | 47.426ns | 1.00 |
GetGenericTypeDefinition | .NET 8.0 | 3.289ns | 0.07 |
これらはすべて詳細ですが、リフレクションのパフォーマンスに最も影響を与えるのは MethodBase.Invoke です。コンパイル時にメソッドのシグネチャが認識され、メソッドはリフレクション経由で呼び出されます。CreateDelegateを使用して、このメソッドのデリゲートを取得してキャッシュし、このデリゲートを通じてすべての呼び出しを実行できます。これによりパフォーマンスのが、コンパイル時にメソッドのシグネチャがわからない場合は、動的メソッドに依存する必要があります。たとえば、MethodBase.Invoke の場合、このメソッドはパフォーマンスが低下し、時間がかかり。.NET開発このオーバーヘッドを回避するために Emit を使用する人もいます。このメソッドは .NET7 で使用されます。.NET8 では、このような多くの状況に対応するための改善が行われています。以前は、エミッタは常に ref/out パラメータに対応できるコードを生成していましたが、多くのメソッドはそのようなパラメータを提供していませんでした。これらの要素を考慮する必要がない場合、生成されたコードはより効率的になる可能性があります。
// If you have .NET 6 installed, you can update the csproj to include a net6.0 in the target frameworks, and then run:
// dotnet run -c Release -f net6.0 --filter "*" --runtimes net6.0 net7.0 net8.0
// Otherwise, you can run:
// dotnet run -c Release -f net7.0 --filter "*" --runtimes net7.0 net8.0
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Reflection;
BenchmarkSwitcher.FromAssembly(typeof(Tests).Assembly).Run(args);
[HideColumns("Error", "StdDev", "Median", "RatioSD")]
public class Tests
{
private MethodInfo _method0, _method1, _method2, _method3;
private readonly object[] _args1 = new object[] { 1 };
private readonly object[] _args2 = new object[] { 2, 3 };
private readonly object[] _args3 = new object[] { 4, 5, 6 };
[GlobalSetup]
public void Setup()
{
_method0 = typeof(Tests).GetMethod("MyMethod0", BindingFlags.NonPublic | BindingFlags.Static);
_method1 = typeof(Tests).GetMethod("MyMethod1", BindingFlags.NonPublic | BindingFlags.Static);
_method2 = typeof(Tests).GetMethod("MyMethod2", BindingFlags.NonPublic | BindingFlags.Static);
_method3 = typeof(Tests).GetMethod("MyMethod3", BindingFlags.NonPublic | BindingFlags.Static);
}
[Benchmark] public void Method0() => _method0.Invoke(null, null);
[Benchmark] public void Method1() => _method1.Invoke(null, _args1);
[Benchmark] public void Method2() => _method2.Invoke(null, _args2);
[Benchmark] public void Method3() => _method3.Invoke(null, _args3);
private static void MyMethod0() { }
private static void MyMethod1(int arg1) { }
private static void MyMethod2(int arg1, int arg2) { }
private static void MyMethod3(int arg1, int arg2, int arg3) { }
}
.NET6、7、および 8 の状況は次のとおりです。
方法 | ランタイム | 平均値 | 比率 |
---|---|---|---|
メソッド0 | .NET 6.0 | 91.457ns | 1.00 |
メソッド0 | .NET 7.0 | 7.205ns | 0.08 |
メソッド0 | .NET 8.0 | 5.719ns | 0.06 |
方法1 | .NET 6.0 | 132.832ns | 1.00 |
方法1 | .NET 7.0 | 26.151ns | 0.20 |
方法1 | .NET 8.0 | 21.602ns | 0.16 |
方法2 | .NET 6.0 | 172.224ns | 1.00 |
方法2 | .NET 7.0 | 37.937ns | 0.22 |
方法2 | .NET 8.0 | 26.951ns | 0.16 |
方法3 | .NET 6.0 | 211.247ns | 1.00 |
方法3 | .NET 7.0 | 42.988ns | 0.20 |
方法3 | .NET 8.0 | 34.112ns | 0.16 |
ここにはいくつかの問題があり、各呼び出しにパフォーマンスのオーバーヘッドが関係し、各呼び出しが繰り返されます。これらの繰り返しのタスクを抽出してキャッシュできれば。より良いパフォーマンスを達成できます。.NET8 は、MethodInvoker 型と ConstructorInvoker 型を通じてこれらの関数を実装します。これらは、MethodBase.Invoke が処理する一般的なエラー (特に Type.Missing の認識と処理など) をすべてカバーしているわけではありませんが、他のすべてのケースに対して、ビルド時の計画でシグネチャが不明なメソッドへの繰り返し呼び出しを最適化するための優れた回避策を提供します。
// dotnet run -c Release -f net8.0 --filter "*"
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Reflection;
BenchmarkSwitcher.FromAssembly(typeof(Tests).Assembly).Run(args);
[HideColumns("Error", "StdDev", "Median", "RatioSD")]
public class Tests
{
private readonly object _arg0 = 4, _arg1 = 5, _arg2 = 6;
private readonly object[] _args3 = new object[] { 4, 5, 6 };
private MethodInfo _method3;
private MethodInvoker _method3Invoker;
[GlobalSetup]
public void Setup()
{
_method3 = typeof(Tests).GetMethod("MyMethod3", BindingFlags.NonPublic | BindingFlags.Static);
_method3Invoker = MethodInvoker.Create(_method3);
}
[Benchmark(Baseline = true)]
public void MethodBaseInvoke() => _method3.Invoke(null, _args3);
[Benchmark]
public void MethodInvokerInvoke() => _method3Invoker.Invoke(null, _arg0, _arg1, _arg2);
private static void MyMethod3(int arg1, int arg2, int arg3) { }
}
.NET8の状況は次のとおりです。
方法 | 平均値 | 比率 |
---|---|---|
MethodBaseInvoke | 32.42ns | 1.00 |
MethodInvokerInvoke | 11.47ns | 0.35 |
これらの型は、DI サービスのビルド パフォーマンスをさらに向上させるために、Microsoft.Extensions.DependencyInjection.Abstractions の ActivatorUtilities.CreateFactory メソッドによって使用されます。これは、すべてのビルドでの反映をさらに回避するために追加のキャッシュ レイヤーを追加することでさらに改善されました。
著者:ジャンフプト
記事が最初に公開される公式アカウント (jianghupt) をフォローしてください。
IntelliJ IDEA 2023.3 と JetBrains Family Bucket の年次メジャー バージョン アップデート 新しいコンセプト「防御型プログラミング」: 安定した仕事に就く GitHub.com では 1,200 を超える MySQL ホストが稼働していますが、8.0 にシームレスにアップグレードするにはどうすればよいですか? Stephen Chow の Web3 チームは来月、独立したアプリをリリースする予定ですが、 Firefox は廃止されるのでしょうか? Visual Studio Code 1.85 がリリース、フローティング ウィンドウ 米国 CISA、メモリ セキュリティの脆弱性を排除するために C/C++ の放棄を推奨 余成東 : ファーウェイは来年破壊的製品を発売し、業界の歴史を書き換える TIOBE 12 月: C# は今年のプログラミング言語になると期待される 論文30年前にLei Junが書いた「コンピュータウイルス判定エキスパートシステムの原理と設計」