ジェネリックがパッケージングから私たちを救う方法

メソッドを呼び出すときに、nullチェックを実行することがよくあります。 誰かが別のメソッドでチェックアウトすると、コードはよりきれいに見え、次のようになります。


public void ThrowIfNull(object obj) { if(obj == null) { throw new ArgumentNullException(); } } 

そして、このようなチェックで興味深いのは、ジェネリックを使用できるため、オブジェクト属性の大規模な使用が見られることです。 メソッドを汎用に置き換えて、パフォーマンスを比較してみましょう。


テストする前に、オブジェクト引数のもう1つの欠点があります。 値の型をnullにすることはできません(Nullable型はカウントしません)。 ThrowIfNull(5)などのメソッド呼び出しは無意味ですが、引数の型はオブジェクトであるため、コンパイラはメソッドの呼び出しを許可します。 私にとっては、これはコードの品質を低下させます。状況によっては、パフォーマンスよりもはるかに重要です。 この動作を取り除き、メソッドのシグネチャを改善するには、ジェネリックメソッドを2つに分割して、制約を示す必要があります。 問題は、Nullable制約を指定できないことですが、struct制約を使用してNullable引数を指定できます。


パフォーマンスのテストを開始し、 BenchmarkDotNetライブラリを使用します。 属性を添付して実行し、結果を確認します。


 public class ObjectArgVsGenericArg { public string str = "some string"; public Nullable<int> num = 5; [MethodImpl(MethodImplOptions.NoInlining)] public void ThrowIfNullGenericArg<T>(T arg) where T : class { if (arg == null) { throw new ArgumentNullException(); } } [MethodImpl(MethodImplOptions.NoInlining)] public void ThrowIfNullGenericArg<T>(Nullable<T> arg) where T : struct { if(arg == null) { throw new ArgumentNullException(); } } [MethodImpl(MethodImplOptions.NoInlining)] public void ThrowIfNullObjectArg(object arg) { if(arg == null) { throw new ArgumentNullException(); } } [Benchmark] public void CallMethodWithObjArgString() { ThrowIfNullObjectArg(str); } [Benchmark] public void CallMethodWithObjArgNullableInt() { ThrowIfNullObjectArg(num); } [Benchmark] public void CallMethodWithGenericArgString() { ThrowIfNullGenericArg(str); } [Benchmark] public void CallMethodWithGenericArgNullableInt() { ThrowIfNullGenericArg(num); } } class Program { static void Main(string[] args) { var summary = BenchmarkRunner.Run<ObjectArgVsGenericArg>(); } } 

方法平均エラーStddev
CallMethodWithObjArgString1.784 ns0.0166 ns0.0138 ns
CallMethodWithObjArgNullableInt124.335 ns0.2480 ns0.2320 ns
CallMethodWithGenericArgString1.746 ns0.0290 ns0.0271 ns
CallMethodWithGenericArgNullableInt2.158 ns0.0089 ns0.0083 ns

私たちの一般的なnullable型は2000倍速く動作しました! そしてすべては、悪名高いパッケージング(ボクシング)によるものです。 CallMethodWithObjArgNullableIntを呼び出すと、nullable-intが「パック」されてヒープに配置されます。 パッケージングは​​非常に高価な操作であり、この方法ではパフォーマンスも低下します。 したがって、ジェネリックを使用すると、パッケージ化を回避できます。


したがって、次の理由により、ジェネリック引数はオブジェクトよりも優れています。


  1. パッケージングから節約
  2. 制限を使用するときにメソッドのシグネチャを改善できます

更新しました。 コメントを寄せてくれたzelyony habrayuzerに感謝します。 インラインのメソッドでは、より正確な測定のために、 MethodImpl(MethodImplOptions.NoInlining)属性が追加されました。



Source: https://habr.com/ru/post/J332640/


All Articles