2

ILGenerator を使用して記述された配列アクセスを含むシンプルな for ループがあります。この正確なコードでメソッドが作成されたら、逆アセンブリを開きますが、配列の境界チェックはありません。

しかし、最初に他のクラスのインスタンスを評価スタックに置いてから for ループを実行すると、配列の境界チェックが行われます。リリースに向けて動いています。

理由はありますか?配列境界チェックに関するブログ投稿を既に読みました: http://blogs.msdn.com/b/clrcodegeneration/archive/2009/08/13/array-bounds-check-elimination-in-the-clr.aspx

        // Uncomment this to enable bound checks, type of arg0 is some my class
        //il.Emit(OpCodes.Ldarg_0);

        var startLbl = il.DefineLabel();
        var testLbl = il.DefineLabel();
        var index = il.DeclareLocal(typeof(Int32));
        var arr = il.DeclareLocal(typeof(Int32).MakeArrayType());

        // arr = new int[4];
        il.Emit(OpCodes.Ldc_I4_4);
        il.Emit(OpCodes.Newarr, typeof(Int32));
        il.Emit(OpCodes.Stloc, arr);

        // Index = 0
        il.Emit(OpCodes.Ldc_I4_0); // Push index
        il.Emit(OpCodes.Stloc, index); // Pop index, store

        il.Emit(OpCodes.Br_S, testLbl); // Go to test

        // Begin for
        il.MarkLabel(startLbl);

        // Load array, index
        il.Emit(OpCodes.Ldloc, arr);
        il.Emit(OpCodes.Ldloc, index);

        // Now on stack: array, index
        // Load element
        il.Emit(OpCodes.Ldelem_I4);
        // Nothing here now, later some function call
        il.Emit(OpCodes.Pop);

        // Index++
        il.Emit(OpCodes.Ldloc, index);
        il.Emit(OpCodes.Ldc_I4_1);
        il.Emit(OpCodes.Add);
        il.Emit(OpCodes.Stloc, index);

        il.MarkLabel(testLbl);
        // Load index, count, test for end
        il.Emit(OpCodes.Ldloc, index);
        il.Emit(OpCodes.Ldloc, arr);
        il.Emit(OpCodes.Ldlen); // Push len
        il.Emit(OpCodes.Conv_I4); // Push len
        il.Emit(OpCodes.Blt_S, startLbl);
        // End for

        // Remove instance added on top
        //il.Emit(OpCodes.Pop);

IL コードを生成するときに、クラスのインスタンスを評価スタックまたはローカル変数に保持する方がよいでしょうか?

たとえば、インスタンスを取得し、フィールドを通過し、各フィールドに対して何かを実行してから戻ります。インスタンスをスタックに保持し、次のフィールドを読み取る前に Emit(OpCodes.Dup) を呼び出しました。しかし、それは間違っているようです(少なくとも上記の場合)。

(効率的で整形式の) IL コードの生成に関する記事やブログ投稿を歓迎します。

4

2 に答える 2

2

一般に、ローカルを使用すると、コードが読みやすくなり、デバッグが容易になります。これは、ほとんどの開発者が読み慣れていない IL を考えると重要なことです。JIT を実行することで発生する可能性のあるパフォーマンスの低下を JIT が解消する可能性さえあります。

ILSpy をいじってみたところ、csc もローカルを好みますが、C# に逆コンパイルするのではなく IL を見たときに認めざるを得ませんが、ほとんどがデバッグ コードでした。JIT はおそらく、ほとんどが Microsoft のコンパイラの出力で実行されることを期待して書かれているため、コンパイラが出力するものと一致しないループ構造を認識しなくても驚くことではありません。余分なスタック エントリが、境界チェックを排除できることを認識する JIT の機能を無効にしている可能性は非常に高いです。

于 2013-03-14T19:07:50.390 に答える
0

メソッドが Jitted される前に、デバッガーをアタッチせずにリリース モードで実行していますか? そして、その後に添付しますか?このステップを実行する必要がないように思われることはわかっていますが、デバッガーが接続されている場合、デバッガーは最適ではないコードを出力します。

エラーがあると思われるメソッド全体を含めます。PEVerify を実行できるように、メソッドを使用してアセンブリを出力することをお勧めします。コンパイルはできるが有効でないコードになる場合があります。

unboxed Tジッタは、無効なコード (たとえば、オブジェクトでのみ実行される一般的なコードで予期されるスタックのような間違ったスタック) で非常に苦労する可能性がboxed Tあり、通常のパターンではない特に検証不可能です。(例: C# または C++/CLI では発生しない安全でないコード)。予期しない限り、常に peverify でエラーを 0 にしようとする必要があります。(例calli)

于 2013-03-18T15:53:27.643 に答える