私は多くのプロパティを実行時に動的にコピーするエンジンで作業しています。状況によっては、途中でプロパティの値を変更する場合と変更しない場合があります。もともとは反射で書かれていましたが、パフォーマンスの問題により、最近Reflection.Emit
に書き直しました。書き直しは完了し、パフォーマンスは明らかに改善されましたが、コードは手書きのC#
に対してベンチマークされています。明らかに、公平な戦いになるためには、ベンチマーク用の手書きのC#
はIL
として「類似の機能」を持っています。C#EMIT ILのパフォーマンスの問題
IL
エンジンの一部は、フライングカラーで渡されたためサインオフされており、手書きのC#
でほぼ1:1です。これは私に語った:
が
私たちの一般的な概念と実装は
ベンチマークは
IL
と手書きC#
が正しい正しい動的メソッドを呼び出すには、オーバーヘッドはありません全く同じ方法でテストされているので、面白いことはありませんJIT
ビジネスが進行中です(私は思っていません)
私たちはIL
が手書きでやや遅くなることを期待していましたが、これまでのところこれまでのところはそうではありません。長いラウンドで数ms遅くなるかもしれませんが、IL
にショートカットを入れることができますので、差分を補うのに役立ちます。
ある特定のケースでは、それは実質的に遅くなります。 2倍遅くなります。 C#
で
、あなたが持っていると思います:
class Source
{
public string S1 { get; set; }
public int I1 { get; set; }
public int I2 { get; set; }
public double D1 { get; set; }
public double D2 { get; set; }
public double D3 { get; set; }
}
class Dest
{
public string S1 { get; set; }
public int I1 { get; set; }
public string I2 { get; set; }
public double D1 { get; set; }
public int D2 { get; set; }
public string D3 { get; set; }
}
static Dest Test(Source s)
{
Dest d = new Dest();
object o = s.D3;
if (o != null)
d.D3 = o.ToString();
return d;
}
これは私が同様の機能によってどのような意味です。ジェネリックであるために、プロパティを文字列にコピーするときは、最初にボックスに入れてObject.ToString()
を呼び出します。ネイティブでは、値のタイプはToString
と異なるので、上記のコードをリンゴにリンゴにします。
D3
コピー/ ToString
と他の5つのプロパティのコメントを外すと、C#
で1:1に戻ります。
あなたはI2
がint
であることがわかります - >string
が、いくつかの理由で、1つはdouble
と同じ問題を抱えていません - >string
。私はその倍を得るToString()
一般的にはより高価ですが、その費用はC#のコードにも表示されるはずですが、そうではありません。
D3
コピー用のコードは、I2
コピー用のコードと同じです。なぜD3
コピーの巨大なオーバーヘッドですか?
EDIT:
コンパイラが発する:
IL_0000: newobj instance void ConsoleApplication3.Dest::.ctor()
IL_0005: ldarg.0
IL_0006: callvirt instance float64 ConsoleApplication3.Source::get_D3()
IL_000b: box [mscorlib]System.Double
IL_0010: stloc.0
IL_0011: dup
IL_0012: ldloc.0
IL_0013: brtrue.s IL_0018
IL_0015: ldnull
IL_0016: br.s IL_001e
IL_0018: ldloc.0
IL_0019: callvirt instance string [mscorlib]System.Object::ToString()
IL_001e: callvirt instance void ConsoleApplication3.Dest::set_D3(string)
IL_0023: ret
私のコードのこの特定のセクションは、他の場所で行うのDestはオブジェクトのための新しいを放出しません。上記のC#
に見られるように、dupはDestオブジェクトを捨てています。
LocalBuilder localBuilderObject = generator.DeclareLocal(_typeOfObject);
Label labelNull = generator.DefineLabel();
Label labelNotNull = generator.DefineLabel();
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Callvirt, miGetter);
generator.Emit(OpCodes.Box, typeSource);
generator.Emit(OpCodes.Stloc_S, localBuilderObject);
generator.Emit(OpCodes.Dup);
generator.Emit(OpCodes.Ldloc_S, localBuilderObject);
generator.Emit(OpCodes.Brtrue, labelNotNull);
generator.Emit(OpCodes.Ldnull);
generator.Emit(OpCodes.Br, labelNull);
generator.MarkLabel(labelNotNull);
generator.Emit(OpCodes.Ldloc_S, localBuilderObject);
generator.Emit(OpCodes.Callvirt, _miToString);
generator.MarkLabel(labelNull);
generator.Emit(OpCodes.Callvirt,miSetter);
私が述べたように、私は値の型を気にすることなく、一般的にObject::ToString()
を呼び出すことができますので、私はタイプボックス。 Ref型もこのパスを通過します。 C#
コードはこのように振る舞い、時間は1/2になります???
私は今週末この問題を悩ましています。さらなるテストでは、他の値のタイプは1:1です。 int
,long
など何らかの理由でdouble
が問題を引き起こしています。
あなたのコードはちょっと難解です:_this_引数をロードし、フィールド値を取得し、それをボックスに格納してからローカルに格納します。あなたは何を複製しますか?また、あなたが発行する実際のILと、C#がそのバージョンに対して生成する実際のILを含めてください。 –
@ 500-InternalServerError、応答に感謝します。私はILラッパーを取り除くためにオリジナルのポストを更新しました(ただし、他のオペコードや何も追加しません)。複製はDestオブジェクトです。いくつかのdiffs私のコード:stloc_s/ldloc_sの代わりにstloc.0/ldloc.0と分岐対ブランチショート。 Int/longはC#のパフォーマンスが賢明なので1:1です。 – SledgeHammer
このレベルのマイクロベンチマーキングでは、ページ上のコードの位置合わせなどの愚かなことは、あなたを上手くいくのに十分です。また、ジッタによって生成された実際のコードにドリルダウンして、実際の違いがないかどうかを確認する必要があります。単にILを見つめ続けるだけでは助けにならないでしょう。 –