長いテキストが気に入らない人のための結果と結論
100,000回の呼び出し、テストの20回の繰り返し、x86 | 100,000回の呼び出し、テストの20回の繰り返し、x64 | 1,000,000回の呼び出し、10回のテスト反復、x86 | 1,000,000回の呼び出し、10回のテスト反復、x64 | |
---|---|---|---|---|
ダイレクトコール | 最小:1ミリ秒 最大:1ミリ秒 平均:1ミリ秒 中央値:1ミリ秒 腹筋:1 | 最小:1ミリ秒 最大:1ミリ秒 平均:1ミリ秒 中央値:1ミリ秒 腹筋:1 | 最小:7ミリ秒 最大:8ミリ秒 平均:7.5ミリ秒 中央値:7.5ミリ秒 腹筋:1 | 最小:5ミリ秒 最大:6ミリ秒 平均:5.2ミリ秒 中央値:5ミリ秒 腹筋:1 |
リフレクションを介して呼び出します | 最小:32ミリ秒 最大:36ミリ秒 平均:32.75ミリ秒 中央値:32.5 ms リリース:32 | 最小:35ミリ秒 最大:44ミリ秒 平均:36.5ミリ秒 中央値:36ミリ秒 リリース:36 | 最小:333ミリ秒 最大:399ミリ秒 平均:345.5 ms 中央値:338ミリ秒 リリース:45 | 最小:362ミリ秒 最大:385ミリ秒 平均:373.6 ms 中央値:376ミリ秒 リリース:75 |
デリゲートコール | 最小:64ミリ秒 最大:71ミリ秒 平均:65.05ミリ秒 中央値:64.5ミリ秒 リリース:64 | 最小:72ミリ秒 最大:86ミリ秒 平均:75.95ミリ秒 中央値:75ミリ秒 リリース:75 | 最小:659ミリ秒 最大:730ミリ秒 平均:688.8ミリ秒 中央値:689.5ミリ秒 リリース:92 | 最小:746ミリ秒 最大:869ミリ秒 平均:773.4ミリ秒 中央値:765ミリ秒 リリース:153 |
最適化されたデリゲートを介した呼び出し | 最小:16ミリ秒 最大:18ミリ秒 平均:16.2 ms 中央値:16ミリ秒 リリース:16 | 最小:21ミリ秒 最大:25ミリ秒 平均:22.15ミリ秒 中央値:22ミリ秒 リリース:22 | 最小:168ミリ秒 最大:187ミリ秒 平均:172.8ミリ秒 中央値:170.5ミリ秒 リリース:22.7 | 最小:218ミリ秒 最大:245ミリ秒 平均:228.8ミリ秒 中央値:227ミリ秒 リリース:45.4 |
動的な呼び出し | 最小:11ミリ秒 最大:14ミリ秒 平均:11.5ミリ秒 中央値:11ミリ秒 リリース:11 | 最小:12ミリ秒 最大:14ミリ秒 平均:12.5ミリ秒 中央値:12ミリ秒 リリース:12 | 最小:124ミリ秒 最大:147ミリ秒 平均:132.1ミリ秒 中央値:130ミリ秒 リリース:17 | 最小:127ミリ秒 最大:144ミリ秒 平均:131.5ミリ秒 中央値:129.5ミリ秒 リリース:26 |
式を介した呼び出し | 最小:4ミリ秒 最大:4ミリ秒 平均:4ミリ秒 中央値:4ミリ秒 リリース:4 | 最小:4ミリ秒 最大:5ミリ秒 平均:4.15ミリ秒 中央値:4ミリ秒 リリース:4 | 最小:46ミリ秒 最大:55ミリ秒 平均:50ミリ秒 中央値:50.5ミリ秒 リリース:6.7 | 最小:47ミリ秒 最大:51ミリ秒 平均:47.7ミリ秒 中央値:47ミリ秒 リリース:9.4 |
UPD: mayorovpからの新しい出力: Expressionを使用するのが最適です
UPD:そして、 CdEmONが促したように、そのような研究は以前にhabrで公開されました
少しオフトピック、研究の理由について
次のコードを記述します。
同様のコードは非常に頻繁に使用されますが、欠点が1つあります。C#では、ジェネリック型のコレクションを明示的に保存できません。 私が見つけたすべてのアドバイスは、基本的な非ジェネリッククラス、インターフェイス、または抽象クラスを強調表示することになります。これらはストレージ用に示されます。 つまり このようなものが得られます:
私の意見では、次のように書く機能を追加すると便利です。
特に、リフレクションを介してジェネリック型を作成する場合、同様の構造を使用することを考慮してください。
しかし、問題に戻ります。 ここで、特定のインスタンスを取得する必要があるが、非ジェネリックメソッドで取得する必要があると想像してください。 たとえば、オブジェクトを受け入れ、そのタイプに基づいてメソッドはハンドラーを選択する必要があります。
ハンドラーを取得しますが、現在環境ではhandler.Process(obj)の書き込みが許可されておらず、書き込みを行うと、コンパイラーはそのようなメソッドがないことを誓います。
ここでも、次のようなC#開発者によるデザインがあります。
、しかしそれは存在せず、メソッドを呼び出す必要があります(Roslynを考慮したとしても、新しいIDEで既に同様にできますか?)。 これを行うには多くの方法があり、そのうちいくつかの主要な方法を区別できます。 それらは記事の冒頭の表にリストされています。
class SampleGeneric<T> { public long Process(T obj) { return String.Format("{0} [{1}]", obj.ToString(), obj.GetType().FullName).Length; } } class Container { private static Dictionary<Type, object> _instances = new Dictionary<Type, object>(); public static void Register<T>(SampleGeneric<T> instance) { if (false == _instances.ContainsKey(typeof(T))) { _instances.Add(typeof(T), instance); } else { _instances[typeof(T)] = instance; } } public static SampleGeneric<T> Get<T>() { if (false == _instances.ContainsKey(typeof(T))) throw new KeyNotFoundException(); return (SampleGeneric<T>)_instances[typeof(T)]; } public static object Get(Type type) { if (false == _instances.ContainsKey(type)) throw new KeyNotFoundException(); return _instances[type]; } }
同様のコードは非常に頻繁に使用されますが、欠点が1つあります。C#では、ジェネリック型のコレクションを明示的に保存できません。 私が見つけたすべてのアドバイスは、基本的な非ジェネリッククラス、インターフェイス、または抽象クラスを強調表示することになります。これらはストレージ用に示されます。 つまり このようなものが得られます:
public interface ISampleGeneric { } class SampleGeneric<T> : ISampleGeneric // private static Dictionary<Type, ISampleGeneric> _instances = new Dictionary<Type, ISampleGeneric>();
私の意見では、次のように書く機能を追加すると便利です。
// Type expected Dictionary<Type, SampleGeneric<>>
特に、リフレクションを介してジェネリック型を作成する場合、同様の構造を使用することを考慮してください。
typeof(SampleGeneric<>).MakeGenericType(typeof(string))
しかし、問題に戻ります。 ここで、特定のインスタンスを取得する必要があるが、非ジェネリックメソッドで取得する必要があると想像してください。 たとえば、オブジェクトを受け入れ、そのタイプに基づいてメソッドはハンドラーを選択する必要があります。
void NonGenericMethod(object obj) { var handler = Container.Get(obj.GetType()); }
ハンドラーを取得しますが、現在環境ではhandler.Process(obj)の書き込みが許可されておらず、書き込みを行うと、コンパイラーはそのようなメソッドがないことを誓います。
ここでも、次のようなC#開発者によるデザインがあります。
Container.GetInstance<fromtype(obj.GetType())>().Process(obj);
、しかしそれは存在せず、メソッドを呼び出す必要があります(Roslynを考慮したとしても、新しいIDEで既に同様にできますか?)。 これを行うには多くの方法があり、そのうちいくつかの主要な方法を区別できます。 それらは記事の冒頭の表にリストされています。
コードについて
以下は、スポイラーで指定されたコード呼び出しです。 時間測定はコードから削除され、測定はストップウォッチで行われました。 分析のために、絶対ではなく相対実行時間に興味があったので、鉄やその他のパラメーターは重要ではありません。 異なるPCでテストした結果は似ています。
また、呼び出しではプリプロセス時間を考慮に入れていないことに注意してください。プリプロセス時間では、目的のメソッドに到達します。 実際の条件、高負荷のタスクでは、このようなアクションは1回だけ実行され、結果はキャッシュされるため、この時間は分析では問題になりません。
ダイレクトコール
メソッドを直接プルするだけです。 表では、最初の行の直接呼び出しの結果であるAbs値は、常に1です。残りの行では、メソッド呼び出しの他のメソッド(Rel値)の呼び出しの速度が低下していることがわかります。
public static TestResult TestDirectCall(DateTime arg) { var instance = Container.Get<DateTime>(); long summ = 0; for (long i = 0; i < ITERATION_COUNT; i++) { summ += instance.Process(arg); } // return }
リフレクションを介して呼び出します
そもそも使いたい最も簡単で手頃な方法です。 メソッドテーブルからメソッドを取得し、Invokeを介してプルします。 同時に、最も遅い方法の1つです。
public static TestResult TestReflectionCall(object arg) { var instance = Container.Get(arg.GetType()); var method = instance.GetType().GetMethod("Process"); long summ = 0; for (long i = 0; i < ITERATION_COUNT; i++) { summ += (long)method.Invoke(instance, new object[] { arg }); } // return }
デリゲートおよび追加の最適化を使用したデリゲートを介した呼び出し
デリゲートを作成するためのコード
private static Delegate CreateDelegate(object target, MethodInfo method) { var methodParameters = method.GetParameters(); var arguments = methodParameters.Select(d => Expression.Parameter(d.ParameterType, d.Name)).ToArray(); var instance = target == null ? null : Expression.Constant(target); var methodCall = Expression.Call(instance, method, arguments); return Expression.Lambda(methodCall, arguments).Compile(); }
したがって、テストコードは次のようになります。
public static TestResult TestDelegateCall(object arg) { var instance = Container.Get(arg.GetType()); var hook = CreateDelegate(instance, instance.GetType().GetMethod("Process")); long summ = 0; for (long i = 0; i < ITERATION_COUNT; i++) { summ += (long)hook.DynamicInvoke(arg); } // return }
Reflectionメソッドに比べて2倍遅くなりますが、このメソッドをスローできますが、プロセスを高速化する素晴らしい方法があります。 正直なところ、私はImpromptuプロジェクト、つまりこの場所で彼をスパイしました。
デリゲートコール最適化コード
internal static object FastDynamicInvokeDelegate(Delegate del, params dynamic[] args) { dynamic tDel = del; switch (args.Length) { default: try { return del.DynamicInvoke(args); } catch (TargetInvocationException ex) { throw ex.InnerException; } #region Optimization case 1: return tDel(args[0]); case 2: return tDel(args[0], args[1]); case 3: return tDel(args[0], args[1], args[2]); case 4: return tDel(args[0], args[1], args[2], args[3]); case 5: return tDel(args[0], args[1], args[2], args[3], args[4]); case 6: return tDel(args[0], args[1], args[2], args[3], args[4], args[5]); case 7: return tDel(args[0], args[1], args[2], args[3], args[4], args[5], args[6]); case 8: return tDel(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]); case 9: return tDel(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]); case 10: return tDel(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9]); case 11: return tDel(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10]); case 12: return tDel(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11]); case 13: return tDel(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12]); case 14: return tDel(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13]); case 15: return tDel(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14]); case 16: return tDel(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15]); #endregion } }
テストコードを少し変更する
public static TestResult TestDelegateOptimizeCall(object arg) { var instance = Container.Get(arg.GetType()); var hook = CreateDelegate(instance, instance.GetType().GetMethod("Process")); long summ = 0; for (long i = 0; i < ITERATION_COUNT; i++) { summ += (long)FastDynamicInvokeDelegate(hook, arg); } // return }
また、通常のデリゲート呼び出しと比較して10倍の加速が得られます。 現時点では、これは考慮されているものの中で最良のオプションです。
動的な呼び出し
そして、メインキャラクターに進みます(もちろん、.NET 4.0より前に作成されたレガシープロジェクトをサポートしていない場合)。
public static TestResult TestDynamicCall(dynamic arg) { var instance = Container.Get(arg.GetType()); dynamic hook = CreateDelegate(instance, instance.GetType().GetMethod("Process")); long summ = 0; for (long i = 0; i < ITERATION_COUNT; i++) { summ += hook(arg); } // return }
デリゲートを介した呼び出しと比較して行ったことすべてに、動的キーワードを追加しました。これにより、ランタイムは、操作中にDLR自体を介してデリゲート呼び出しを構築できました。 実際、彼らは型チェックチェックを投げました。 また、最適化されたデリゲートの呼び出しに比べて2倍に加速しました。
UPD:プロンプトmayorovpで呼び出すより効率的な方法を追加しました。 速度で最高の結果を示し、ダイナミックと比較してメモリの消費が少なくなります。
delegate object Invoker(object target, params object[] args); static Invoker CreateExpression(MethodInfo method) { var targetArg = Expression.Parameter(typeof(object)); var argsArg = Expression.Parameter(typeof(object[])); Expression body = Expression.Call( method.IsStatic ? null : Expression.Convert(targetArg, method.DeclaringType), method, method.GetParameters().Select((p, i) => Expression.Convert(Expression.ArrayIndex(argsArg, Expression.Constant(i)), p.ParameterType))); if (body.Type == typeof(void)) body = Expression.Block(body, Expression.Constant(null)); else if (body.Type.IsValueType) body = Expression.Convert(body, typeof(object)); return Expression.Lambda<Invoker>(body, targetArg, argsArg).Compile(); }
テスト
public static TestResult TestExpressionCall(object arg) { var instance = Container.Get(arg.GetType()); var hook = CreateExpression(instance.GetType().GetMethod("Process")); long summ = 0; for (long i = 0; i < ITERATION_COUNT; i++) { summ += (long)hook(instance, arg); } //return }
プロジェクトコード
結果に戻る