動的呼び出し:メソッドの比較


動的呼び出し:それは何で、なぜですか?



静的プログラミング言語で作業しているすべての開発者にとって、動的呼び出しに頼る必要があったこともあると思います-まだ何も知られていない何かのメソッドを呼び出すために。 または、実行時にのみ認識されるオブジェクトからプロパティを取得します。

これは、いわゆるダックタイピングに基づくアルゴリズムで使用されることがあります。
何かがカモのように見え、カモのように泳ぎ、カモがカモのように鳴る場合、これはおそらくカモです。


この記事では、Microsoft .NET 4.0で利用可能な主な方法を検討し、そのパフォーマンスと構文を比較したいと思います。


利用可能な主なオプション



最初に、この記事で分析するオプションをリストします。 オプションのいずれかが面白くないと思われる場合は、スキップしてください。 そして、検討する価値のある他のオプションがある場合は、書いてください、私はそれらをテキストに追加します。

ここで説明する方法は完全に相互交換可能ではないことをすぐに警告します。それぞれの方法には独自の特性と範囲があります。 使用の複雑さと、動的メソッドのアクセス/呼び出しの速度を比較したかった。 これにより、特定のタスクに基づいて特定の方法を選択できるようになります。

だから:

-反射(反射、RTTI、イントロスペクション)
-意味の辞書
-代表者の辞書
-静的コンテナを持つ動的オブジェクト
-Expandoを使用した動的オブジェクト
-コールピックアップを使用した動的オブジェクト
-式のコンパイル(式)

テスト方法



したがって、目標はフィールド/メソッドへのアクセス速度を比較することなので、次のような一般的な構造でテストを設計しました。
-テストされたオブジェクトを含むテストコンテナオブジェクト(ObjectTester)があります。
-テストオブジェクトには2つのプロパティが含まれている必要があります-1つの整数(A)と1つの文字列(B)
-テストオブジェクトは、AおよびBへの呼び出しをテストオブジェクトに委任します。
-テスト環境は2000個のコンテナオブジェクトの配列を作成し、それらにデータを入力します
-テスト環境は、配列内の各オブジェクトに対して2000回テスト操作を実行します
-テスト操作は、プロパティAとBの値の少なくとも2倍を受け取る必要があります(ソリューション内のキャッシュの存在をチェックできるようにするために2回)
-テスト方法ごとに、テストが5回実行されます

コードはリリース構成でコンパイルされます。
テストは、Windows 7 x64、.NET 4.0.30319、Intel Core 2 Quad Q9400(2666 MHz)、4GB DDR2で実行されます

静的(非動的)実装


比較のために、ObjectTesterが参照ポイントとして追加されました。これには、2つの自動プロパティを持つ単純なクラスが含まれ、このクラスへの呼び出しを委任します。

この方法は完全に非動的であるため、最速になります。 したがって、これに関して他の方法を比較します。

コードは非常に単純です(ちなみに、静的クラスを思い出してください。これも使用します)。
private class StaticObject
{
public int A { get ; set ; }
public string B { get ; set ; }
}

private class StaticObjectTester : ObjectTester
{
private readonly StaticObject _o = new StaticObject();

public override int A
{
get { return _o.A; }
set { _o.A = value ; }
}

public override string B
{
get { return _o.B; }
set { _o.B = value ; }
}
}


* This source code was highlighted with Source Code Highlighter .
private class StaticObject
{
public int A { get ; set ; }
public string B { get ; set ; }
}

private class StaticObjectTester : ObjectTester
{
private readonly StaticObject _o = new StaticObject();

public override int A
{
get { return _o.A; }
set { _o.A = value ; }
}

public override string B
{
get { return _o.B; }
set { _o.B = value ; }
}
}


* This source code was highlighted with Source Code Highlighter .


結果: 00:00:06.6274538(+ 0.00%)

長所:
-可能な限り高速
短所
-動的ではない

反射(反射、RTTI、イントロスペクション)



これは、ダイナミックコールが必要なときに頭に浮かぶ最初の方法だと思います。

可能な限り高速化するために、静的キャッシングPropertyInfoを使用しました。 通常、実際のプロジェクトでは、リフレクションに必要な情報を何らかの方法でキャッシュする機会が常にあります。

リフレクションの速度に関心があるため、データを格納するために2つのプロパティを持つ既製のクラスを使用します。

テストオブジェクトコード:
private class ReflectionObjectTester : ObjectTester
{
private static readonly PropertyInfo PropertyA = typeof (StaticObject).GetProperty( "A" );
private static readonly PropertyInfo PropertyB = typeof (StaticObject).GetProperty( "B" );
private static readonly object [] Empty = new object [0];
private readonly Object _o = new StaticObject();

public override int A
{
get { return ( int ) PropertyA.GetValue(_o, Empty); }
set { PropertyA.SetValue(_o, value , Empty); }
}

public override string B
{
get { return ( string ) PropertyB.GetValue(_o, Empty); }
set { PropertyB.SetValue(_o, value , Empty); }
}
}


* This source code was highlighted with Source Code Highlighter .
private class ReflectionObjectTester : ObjectTester
{
private static readonly PropertyInfo PropertyA = typeof (StaticObject).GetProperty( "A" );
private static readonly PropertyInfo PropertyB = typeof (StaticObject).GetProperty( "B" );
private static readonly object [] Empty = new object [0];
private readonly Object _o = new StaticObject();

public override int A
{
get { return ( int ) PropertyA.GetValue(_o, Empty); }
set { PropertyA.SetValue(_o, value , Empty); }
}

public override string B
{
get { return ( string ) PropertyB.GetValue(_o, Empty); }
set { PropertyB.SetValue(_o, value , Empty); }
}
}


* This source code was highlighted with Source Code Highlighter .
private class ReflectionObjectTester : ObjectTester
{
private static readonly PropertyInfo PropertyA = typeof (StaticObject).GetProperty( "A" );
private static readonly PropertyInfo PropertyB = typeof (StaticObject).GetProperty( "B" );
private static readonly object [] Empty = new object [0];
private readonly Object _o = new StaticObject();

public override int A
{
get { return ( int ) PropertyA.GetValue(_o, Empty); }
set { PropertyA.SetValue(_o, value , Empty); }
}

public override string B
{
get { return ( string ) PropertyB.GetValue(_o, Empty); }
set { PropertyB.SetValue(_o, value , Empty); }
}
}


* This source code was highlighted with Source Code Highlighter .



結果 :00:01:32.3217880(+ 1293.02%)

実際、リフレクションを使用すると、実行時間が約13倍に増加しました。 この方法を使用する前に考える良い方法です。

長所:
-完全実行時間
短所:
-長すぎる
-権利「ReflectionPermission」の要件により問題が発生する場合があります

値の辞書



このメソッドの意味は、辞書に値を保存することです(Dictionary <TKey、TValue>)。 キーとして、値としてフィールドの名前を取得します-フィールドの値。 値型としてObjectを使用する必要があります。

コード:
private class DictionaryObjectTester : ObjectTester
{
private const string AName = "A" ;
private const string BName = "B" ;
private readonly Dictionary< string , Object> _o = new Dictionary< string , object >();

public override int A
{
get { return ( int ) _o[AName]; }
set { _o[AName] = value ; }
}

public override string B
{
get { return ( string ) _o[BName]; }
set { _o[BName] = value ; }
}
}


* This source code was highlighted with Source Code Highlighter .
private class DictionaryObjectTester : ObjectTester
{
private const string AName = "A" ;
private const string BName = "B" ;
private readonly Dictionary< string , Object> _o = new Dictionary< string , object >();

public override int A
{
get { return ( int ) _o[AName]; }
set { _o[AName] = value ; }
}

public override string B
{
get { return ( string ) _o[BName]; }
set { _o[BName] = value ; }
}
}


* This source code was highlighted with Source Code Highlighter .


結果: 00:00:10.8516518(+ 63.64%)

長所:
-非常に動的:追加および削除できます
短所:
-値の弱い型付け(オブジェクト)
-メソッドではなく値のみ

デリゲート辞書



実際には、少し複雑なバージョンがよく使用されます-値を保存するのではなく、これらの値を取得する方法を保存します。 または、機能/アクションの動的なセットのみ。

実装:
private class DictionaryDelegateTester : ObjectTester
{
private const string AName = "A" ;
private const string BName = "B" ;

private readonly Dictionary< string , Func<Object>> _getters;
private readonly Dictionary< string , Action<Object>> _setters;

private readonly StaticObject _o = new StaticObject();

public DictionaryDelegateTester()
{
_getters = new Dictionary< string , Func<Object>>
{
{AName, () => _o.A},
{BName, () => _o.B}
};
_setters = new Dictionary< string , Action< object >>
{
{AName, v => _o.A = ( int ) v},
{BName, v => _o.B = v.ToString()},
};
}

public override int A
{
get { return ( int ) _getters[AName](); }
set { _setters[AName]( value ); }
}

public override string B
{
get { return ( string ) _getters[BName](); }
set { _setters[BName]( value ); }
}
}


* This source code was highlighted with Source Code Highlighter .
private class DictionaryDelegateTester : ObjectTester
{
private const string AName = "A" ;
private const string BName = "B" ;

private readonly Dictionary< string , Func<Object>> _getters;
private readonly Dictionary< string , Action<Object>> _setters;

private readonly StaticObject _o = new StaticObject();

public DictionaryDelegateTester()
{
_getters = new Dictionary< string , Func<Object>>
{
{AName, () => _o.A},
{BName, () => _o.B}
};
_setters = new Dictionary< string , Action< object >>
{
{AName, v => _o.A = ( int ) v},
{BName, v => _o.B = v.ToString()},
};
}

public override int A
{
get { return ( int ) _getters[AName](); }
set { _setters[AName]( value ); }
}

public override string B
{
get { return ( string ) _getters[BName](); }
set { _setters[BName]( value ); }
}
}


* This source code was highlighted with Source Code Highlighter .


結果: 00:00:12.3299023(+ 85.93%)

長所:
-非常に動的:追加および削除できます
短所:
-複雑な構文

静的コンテナを持つ動的オブジェクト



さて、ここで新しいタイプの「ダイナミック」に進みます。 彼のために、私はすぐに3つの方法を適用します。 1つ目は、動的の「バッキングオブジェクト」と同じ静的型を格納する場合です。

private class DynamicObjectTester : ObjectTester
{
private readonly dynamic _o = new StaticObject();

public override int A
{
get { return _o.A; }
set { _o.A = value ; }
}

public override string B
{
get { return _o.B; }
set { _o.B = value ; }
}
}


* This source code was highlighted with Source Code Highlighter .
private class DynamicObjectTester : ObjectTester
{
private readonly dynamic _o = new StaticObject();

public override int A
{
get { return _o.A; }
set { _o.A = value ; }
}

public override string B
{
get { return _o.B; }
set { _o.B = value ; }
}
}


* This source code was highlighted with Source Code Highlighter .


結果: 00:00:10.7193446(+ 61.65%)

長所:
-DLRで送信可能(F#)
-簡単な構文
短所:
-しかも1.5倍長い

Expandoを使用した動的オブジェクト


実際、動的を使用するのがより便利になるように、 ExpandoObject型を追加しました。 それは多くの素敵な小さなものを持っています-それはIDictionaryとIEnumerableの両方です。

使用法:
private class ExpandoDynamicObjectTester : ObjectTester
{
private readonly dynamic _o = new ExpandoObject();

public override int A
{
get { return _o.A; }
set { _o.A = value ; }
}

public override string B
{
get { return _o.B; }
set { _o.B = value ; }
}
}


* This source code was highlighted with Source Code Highlighter .
private class ExpandoDynamicObjectTester : ObjectTester
{
private readonly dynamic _o = new ExpandoObject();

public override int A
{
get { return _o.A; }
set { _o.A = value ; }
}

public override string B
{
get { return _o.B; }
set { _o.B = value ; }
}
}


* This source code was highlighted with Source Code Highlighter .


結果: 00:00:09.7034082(+ 46.33%)

長所:
-DLRで送信可能(F#)
-シンプルな構文と追加機能(列挙)
短所:
-速度の増加を除いて、実質的に不在

コールピックアップの動的オブジェクト



ダイナミックのもう1つの優れた機能は、メソッドとプロパティの呼び出しをインターセプトする機能です。 以下を使用します。
private class PureDynamicObjectTester : ObjectTester
{
private readonly dynamic _o = new DynamicContainer();

public override int A
{
get { return _o.A; }
set { _o.A = value ; }
}

public override string B
{
get { return _o.B; }
set { _o.B = value ; }
}

#region Nested type: DynamicContainer

private class DynamicContainer : DynamicObject
{
private int _a;
private string _b;

public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (binder.Name == "A" )
{
result = _a;
return true ;
}
if (binder.Name == "B" )
{
result = _b;
return true ;
}
return base .TryGetMember(binder, out result);
}

public override bool TrySetMember(SetMemberBinder binder, object value )
{
if (binder.Name == "A" )
{
_a = ( int ) value ;
return true ;
}
if (binder.Name == "B" )
{
_b = ( string ) value ;
return true ;
}
return base .TrySetMember(binder, value );
}
}

#endregion
}


* This source code was highlighted with Source Code Highlighter .
private class PureDynamicObjectTester : ObjectTester
{
private readonly dynamic _o = new DynamicContainer();

public override int A
{
get { return _o.A; }
set { _o.A = value ; }
}

public override string B
{
get { return _o.B; }
set { _o.B = value ; }
}

#region Nested type: DynamicContainer

private class DynamicContainer : DynamicObject
{
private int _a;
private string _b;

public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (binder.Name == "A" )
{
result = _a;
return true ;
}
if (binder.Name == "B" )
{
result = _b;
return true ;
}
return base .TryGetMember(binder, out result);
}

public override bool TrySetMember(SetMemberBinder binder, object value )
{
if (binder.Name == "A" )
{
_a = ( int ) value ;
return true ;
}
if (binder.Name == "B" )
{
_b = ( string ) value ;
return true ;
}
return base .TrySetMember(binder, value );
}
}

#endregion
}


* This source code was highlighted with Source Code Highlighter .


少し長かった。

結果: 00:00:11.1040041(+ 67.45%)

長所:
-DLRで送信可能(F#)
-プロパティが存在しない場合もあります
短所:
-かなり多くのコードが必要

式のコンパイル



このメソッドはダイナミックを使用しませんが(ダイナミックオブジェクトでラップするのは非常に簡単ですが)、リフレクションを使用するメソッドに最も近い方法です。 実際、彼はリフレクションを使用していますが、それは式ツリーを構築してコンパイルするためだけです。

実際、ILコードの「放出」が発生します。 その結果、速度が大幅に向上します。

この実装を作成しました。実際のゲッターとセッターを取得するためにpropertyInfoの2つの拡張メソッドを作成しましたが、通常のリフレクションの場合のMethodInfoの形式ではなく、Func(getter)およびAction(setter)の形式です。 実際、私のゲッターは「 o => ((T)o).{name} 」のように見え、セッターは「 (o, v) => ((T)o).{name} = v 」のようになります。

これらのヘルパーメソッドを読みやすくするために、各式ノードを別々の変数に入れています。
public static Func< object , T> GetValueGetter<T>( this PropertyInfo propertyInfo)
{
var instance = Expression.Parameter( typeof (Object), "i" );
var castedInstance = Expression.ConvertChecked(instance, propertyInfo.DeclaringType);
var property = Expression.Property(castedInstance, propertyInfo);
var convert = Expression. Convert (property, typeof (T));
var expression = Expression.Lambda(convert, instance);
return (Func< object , T>)expression.Compile();
}

public static Action< object ,T> GetValueSetter<T>( this PropertyInfo propertyInfo)
{
var instance = Expression.Parameter( typeof (Object), "i" );
var castedInstance = Expression.ConvertChecked(instance, propertyInfo.DeclaringType);
var argument = Expression.Parameter( typeof (T), "a" );
var setterCall = Expression.Call(
castedInstance,
propertyInfo.GetSetMethod(),
Expression. Convert (argument, propertyInfo.PropertyType));
return (Action< object ,T>)Expression.Lambda(setterCall, instance, argument)
.Compile();
}


* This source code was highlighted with Source Code Highlighter .
public static Func< object , T> GetValueGetter<T>( this PropertyInfo propertyInfo)
{
var instance = Expression.Parameter( typeof (Object), "i" );
var castedInstance = Expression.ConvertChecked(instance, propertyInfo.DeclaringType);
var property = Expression.Property(castedInstance, propertyInfo);
var convert = Expression. Convert (property, typeof (T));
var expression = Expression.Lambda(convert, instance);
return (Func< object , T>)expression.Compile();
}

public static Action< object ,T> GetValueSetter<T>( this PropertyInfo propertyInfo)
{
var instance = Expression.Parameter( typeof (Object), "i" );
var castedInstance = Expression.ConvertChecked(instance, propertyInfo.DeclaringType);
var argument = Expression.Parameter( typeof (T), "a" );
var setterCall = Expression.Call(
castedInstance,
propertyInfo.GetSetMethod(),
Expression. Convert (argument, propertyInfo.PropertyType));
return (Action< object ,T>)Expression.Lambda(setterCall, instance, argument)
.Compile();
}


* This source code was highlighted with Source Code Highlighter .


その結果、テストクラスは次のことを行います。
private class ExpressionObjectTester : ObjectTester
{
private static readonly Func< object , int > AGetter =
typeof (StaticObject).GetProperty( "A" ).GetValueGetter< int >();

private static readonly Func< object , string > BGetter =
typeof (StaticObject).GetProperty( "B" ).GetValueGetter< string >();

private static readonly Action< object , int > ASetter =
typeof (StaticObject).GetProperty( "A" ).GetValueSetter< int >();

private static readonly Action< object , string > BSetter =
typeof (StaticObject).GetProperty( "B" ).GetValueSetter< string >();

private readonly StaticObject _o = new StaticObject();

public override int A
{
get { return AGetter(_o); }
set { ASetter(_o, value ); }
}

public override string B
{
get { return BGetter(_o); }
set { BSetter(_o, value ); }
}
}


* This source code was highlighted with Source Code Highlighter .
private class ExpressionObjectTester : ObjectTester
{
private static readonly Func< object , int > AGetter =
typeof (StaticObject).GetProperty( "A" ).GetValueGetter< int >();

private static readonly Func< object , string > BGetter =
typeof (StaticObject).GetProperty( "B" ).GetValueGetter< string >();

private static readonly Action< object , int > ASetter =
typeof (StaticObject).GetProperty( "A" ).GetValueSetter< int >();

private static readonly Action< object , string > BSetter =
typeof (StaticObject).GetProperty( "B" ).GetValueSetter< string >();

private readonly StaticObject _o = new StaticObject();

public override int A
{
get { return AGetter(_o); }
set { ASetter(_o, value ); }
}

public override string B
{
get { return BGetter(_o); }
set { BSetter(_o, value ); }
}
}


* This source code was highlighted with Source Code Highlighter .


結果 :00:00:08.5675928(+ 29.20%)

長所:
-驚くべき速度
短所
-少し複雑な実装

ピボットテーブルと結論



メソッド名時間時間の追加
リフレクション00:01:33.60771391311.59%
値の辞書00:00:10.851651863.64%
デリゲート辞書00:00:12.329902385.93%
静的コンテナを持つ動的オブジェクト00:00:10.719344661.65%
Expandoを使用した動的オブジェクト00:00:09.703408246.33%
コールピックアップの動的オブジェクト00:00:11.104004167.45%
式のコンパイル00:00:08.567592829.20%


結論は非常に簡単です。
-速度が必要な場合-式を使用します。 少し掘り下げてもそれほど怖くはありませんが、従来のリフレクションと比較すると、驚くべき結果が得られます(ダイナミックな場合でも、平均でほぼ2倍高速です)
-動的オブジェクトを使用する必要がある場合(たとえば、DLR-F#、IronRubyと組み合わせて)、ExpandoObjectを使用することをお勧めします。 優れた結果が得られます。
-.NETでのdynamicの実装は、辞書と比較しても非常に効果的です

しかし、最も重要なのは、 本当に必要な場合にのみ動的呼び出しを使用することです

良い例は、構造が弱く定義されたXMLデータの処理です。 悪い例は、アルゴリズムの説明です。 必要なインターフェイスを常に強調表示し、動作を説明する概念で既に操作することをお勧めします。

忘れないでください-動的型は、リファクタリングの可能性、コンパイル段階でエラーをキャッチする可能性、および効果的な最適化の可能性を事実上無効にします。

この記事が、言語の動的な機能を利用することを考えている人々に役立つことを願っています-正しい方法を選択するか、使用しないことに決めます。

コードは、クローンまたは表示 にGoogle Code 入手できます

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


All Articles