1

そのタスク専用の IL を生成することにより、プロジェクト内のコードの一部のパフォーマンスを改善しようとしました。

このタスクは現在、配列の要素に対して for ループを実行し、インターフェイスを介してさまざまなメソッドを実行することによって行われます。仮想/インターフェイスの呼び出しなしで (必要な操作を直接実行することにより)、このタスクを具体的に実行する IL に置き換えたいと考えました。

何らかの理由で、この DynamicMethodの実行時パフォーマンスは、要素ごとにインターフェイス呼び出しを行う元のコードの実行時パフォーマンスよりも大幅に遅くなります。私が見ることができる唯一の理由は、私の DynamicMethod が非常に大きい (配列の要素ごとにいくつかの命令) ことです。

JIT のせいで最初の呼び出しが遅いのではないかと思っていましたが、そうではありません。すべての通話が遅くなります。誰もそのようなことに遭遇しましたか?

編集

ここの人々はコードを要求します..元のコードはかなり大きいですが、縮小版を次に示します (これは、リバース モード AD を使用して関数勾配を計算するための自動微分コードです)。配列内のすべての要素は、次のクラスを継承します

abstract class Element
{
    public double Value
    public double Adjoint
    public abstract void Accept(IVisitor visitor)
}

要素から派生する 2 つのクラスがあります。簡単にするために、次の 2 つだけを定義します。

class Sum : Element
{
    public int IndexOfLeft;   // the index in the array of the first operand
    public int IndexOfRight;  // the index in the array of the second operand
    public abstract void Accept(IVisitor visitor) { visitor.Visit(this); }
}

class Product : Element
{
    public int IndexOfLeft;   // the index in the array of the first operand 
    public int IndexOfRight;  // the index in the array of second first operand 
    public abstract void Accept(IVisitor visitor) { visitor.Visit(this); }
}

ビジターの実装は次のとおりです。

class Visitor : IVisitor
{
    private Element[] array;

    public Visitor(Element[] array) { this.array = array; }

    public void Visit(Product product)
    {
        var left = array[product.IndexOfLeft].Value;
        var right = array[product.IndexOfRight].Value;

        // here we update product.Value and product.Adjoint according to some mathematical formulas involving left & right
    } 

    public void Visit(Sum sum)
    {
        var left = array[sum.IndexOfLeft].Value;
        var right = array[sum.IndexOfRight].Value;

        // here we update sum.Value and product.Adjoint according to some mathematical formulas involving left & right
    }       
}

私の元のコードは次のようになります。

void Compute(Element[] array)
{
    var visitor = new Visitor(array);
    for(int i = 0; i < array.Length; ++i)
        array[i].Accept(visitor);
}

私の新しいコードは、このようなことをしようとします

void GenerateIL(Element[] array, ILGenerator ilGenerator)
{
    for(int i = 0; i < array.Length; ++i)
    {
        // for each element we emit calls that push "array[i]" and "array" 
        // to the stack, treating "i" as constant,
        // and emit a call to a method similar to Visit in the above visitor that 
        // performs a computation similar to Visitor.Visit.
    }
}

次に、生成されたコードを呼び出します。これは、Compute(array); を呼び出すときにビジター パターンを使用したダブル ディスパッチよりも遅く実行されます。

4

4 に答える 4

1

ループをtry-catchブロックで囲むことにより、JITをだましてより高速なメモリを使用させようとしましたか?これには、終了条件を削除するという利点もあるため、ILを少し節約できます。

try
{
    for (int i= 0; ; i++)
    {
        var visitor = new Visitor(array);
        for(int i = 0;; ++i)
            array[i].Accept(visitor);
    }
}
catch (IndexOutOfRangeException)
{ }

ひどいように見えますが、ILパフォーマンスの問題を修正するのに役立つ可能性のあるJITメモリ割り当ての癖を利用しています。

詳細については、forループの最適化を参照してください。

于 2012-10-02T22:29:41.170 に答える
1

コードの超最適化に本当に興味がある場合は、IL を学ぶ必要があります。

次のリンクで IL OP コードを見てください...

http://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes(v=vs.95).aspx

ILDasm を使用して、メソッドから生成したコードを確認することもできます...

IL をあまり最適化することはできず、C++ で記述してアンマネージ コードを呼び出した方がはるかに優れていると思いますが...

あなたへのちょっとした考え...

がんばれマシュー

于 2012-10-04T19:44:22.420 に答える
1

私が正しく理解している場合は、コード自体を発行してメソッドを直接呼び出すことにより、仮想メソッドを呼び出すオーバーヘッドを取り除こうとしています。たとえば、何千もの仮想関数を呼び出す代わりに、1 つの仮想関数を呼び出したい場合などです。

ただし、異なるオブジェクトに同じインターフェイスを持たせたいとします。これは、仮想呼び出しによってのみ実現できます。インターフェイスを実装するか、デリゲートを使用するか、コードを発行します。はい、コードを発行する場合でも、そのメソッドを呼び出すには何らかのインターフェイスが必要です。これは、デリゲートを呼び出すか、関数/アクションの事前定義されたデリゲートにキャストする可能性があります。

コードを発行する効率的な方法が必要な場合は、「LambdaExpression.CompileToMethod」を使用することをお勧めします。そのメソッドはメソッドビルダーを使用しており、すでにあると思います。インターネットの周りで多くの例を見ることができます。それでも、これは仮想通話にもなります。

その結果、多くのオブジェクトで同じインターフェイスを使用したい場合、オブジェクトをそのタイプに関して異なるビンに配置しない限り、非仮想呼び出しを行うことはできません。これはポリモヒズムに反対しています。

于 2012-09-30T10:21:20.030 に答える
1

タイトルが動的メソッドである理由に興味があります。IL を生成しているとき。動的な IL 生成を意味し、次に静的な実行を意味します。または、c# の dynamic キーワードに相当する IL を使用する IL も生成していますか?

動的 (ランタイム) IL

コードが 1 回だけ jit されると仮定します。そして、あなたはこれをチェックしました。

提供されたサンプルでジェネリックではなく配列を使用すると、さらに謎が深まります。問題は、IL を生成するコードではなく、生成された IL にあります。ただし、生成された IL で ARRAY を使用した場合は、box unbox を使用することになります。高価な STACK/HEAP および back 操作。

BOX と UNBOX を使用するコードを IL 生成しますか。IL操作?私はそこから始めます。

次に注目するのは、コレクションの初期化です。

メソッド呼び出しのオーバーヘッドを節約するためにコードの大きなセクションをマークすると、JIT 時間に悪影響を及ぼす可能性があります。コンパイラはメソッド/メンバー全体を処理する必要があるためです。小さなメソッドがある場合は、必要に応じてコンパイルされます。しかし、あなたはそれがJITの問題ではないと言いました。

これらの LARGE メソッドには、大きなスタック操作がある可能性があります。

頻繁に呼び出されるメソッド内の大きな Value オブジェクトはありますか? 例えば ​​> 64 バイトの STRUCT オブジェクト? 毎回スタックの割り当てと破棄。

RedGate パフォーマンス プロファイラーは何を教えてくれますか? http://www.red-gate.com/products/dotnet-development/ants-performance-profiler/?utm_source=google&utm_medium=cpc&utm_content=unmet_need&utm_campaign=antsperformanceprofiler&gclid=CIXamdiA6bICFQYcpQodDA0Akw

ところで、IL の初心者です。そこにいくつかのアイデアを投げかけるだけです。

幸運を。

于 2012-10-05T05:27:19.923 に答える