0

最近、一部の種類の財務計算では、計算のさまざまな段階から数値を取得する必要がある場合は特に、次のパターンをたどってテストする方がはるかに簡単であることがわかりました。

public class nonsensical_calculator
{ 

   ...

    double _rate;
    int _term;
    int _days;

    double monthlyRate { get { return _rate / 12; }}

    public double days { get { return (1 - i); }}
    double ar   { get { return (1+ days) /(monthlyRate  * days)
    double bleh { get { return Math.Pow(ar - days, _term)
    public double raar { get { return bleh * ar/2 * ar / days; }}
    ....
}

明らかに、これにより、特定の式内で同じアクセサへの複数の呼び出しが発生することがよくあります。コンパイラーが、状態の変化を伴わずにこれらの繰り返される呼び出しを最適化するのに十分賢いのかどうか、またはこのスタイルがまともなパフォーマンスの低下を引き起こしているのかどうかについて興味がありました。

さらなる読書の提案は常にありがたいです

4

4 に答える 4

8

私の知る限り、C#コンパイラはこれを最適化しません。これは、副作用を特定できないためです(たとえばaccessCount++、ゲッターにある場合はどうなりますか?)。EricLippertによる優れた回答をここで確認してください。

その答えから:

C#コンパイラは、この種の最適化を行うことはありません。前述のように、これを行うには、コンパイラが呼び出されているコードをピアリングし、計算結果が呼び出し先のコードの存続期間にわたって変更されないことを確認する必要があります。C#コンパイラはそうしません。

JITコンパイラはそうかもしれません。できなかった理由はありません。すべてのコードがそこにあります。プロパティゲッターをインライン化することは完全に自由であり、ジッターがインライン化されたプロパティゲッターがレジスターにキャッシュして再利用できる値を返すと判断した場合は、自由に行うことができます。(値が別のスレッドで変更される可能性があるために変更したくない場合は、すでに競合状態のバグがあります。パフォーマンスを心配する前にバグを修正してください。)

ちょっと注意してください、C#コンパイラチームのエリックとして見て、私は彼の答えを信頼します:)

于 2010-03-23T02:13:52.573 に答える
7

いくつかのランダムな考え。

まず、他の人が指摘しているように、C#コンパイラはこの種の最適化を行いませんが、ジッタは自由に行うことができます。

第二に、パフォーマンスの質問に答える最良の方法は、それを試して見ることです。ストップウォッチクラスはあなたの友達です。両方の方法で10億回試して、どちらが速いかを確認してください。その後、あなたは知っているでしょう。

第三に、もちろん、すでに十分に高速なものを最適化するために時間を費やすことは意味がありません。ベンチマークに多くの時間を費やす前に、プロファイリングとホットスポットの検索に時間を費やしてください。これが1つになる可能性は低いです。

そして第4に、別の回答は、中間結果をローカル変数に格納することを提案しました。そうすることで、状況によってはかなり速くなる場合もあれば、遅くなる場合もあることに注意してください。結果を保存して必要なときに再検索するよりも、不必要に結果を再計算する方が速い場合があります。

どうしてそれができるのでしょうか?レジスタの数が少ないチップアーキテクチャ(私はあなたを見ています、x86)は、どのローカルがレジスタに含まれ、どのローカルがスタックアクセスになるかについて非常に慎重である必要があります。使用頻度の低いものを1つのレジスタに配置するようにジッターを奨励することは、そのレジスタから別の何かを強制することを意味する場合があります。これは、使用頻度の低い値よりもレジスタに存在することでより多くのメリットが得られます。

要するに:快適なアームチェアからのジッターを二度と推測しようとしないでください。実際のコードの動作は、非常に直感に反する可能性があります。現実的な経験的測定に基づいてパフォーマンスを決定します。

于 2010-03-23T04:55:17.800 に答える
3

そうです、C#コンパイラはこのような最適化を行いません。しかし、JITコンパイラは確かにそうです。投稿したすべてのゲッターは、インライン化するのに十分小さいため、フィールドに直接アクセスできます。

例:

static void Main(string[] args) {
  var calc = new nonsensical_calculator(42);
  double rate = calc.monthlyRate;
  Console.WriteLine(rate);
}

生成:

00000000  push        ebp                          ; setup stack frame
00000001  mov         ebp,esp 
00000003  sub         esp,8 
00000006  mov         ecx,349DFCh                  ; eax = new nonsensical_calculator
0000000b  call        FFC50AD4 
00000010  fld         dword ptr ds:[006E1590h]     ; st0 = 42
00000016  fstp        qword ptr [eax+4]            ; _rate = st0
00000019  fld         qword ptr [eax+4]            ; st0 = _rate
0000001c  fdiv        dword ptr ds:[006E1598h]     ; st0 = st0 / 12
00000022  fstp        qword ptr [ebp-8]            ; rate = st0
      Console.WriteLine(rate);
// etc..

コンストラクター呼び出しとプロパティゲッターの両方が消えて、Main()にインライン化されていることに注意してください。コードは_rateフィールドに直接アクセスしています。calc変数がなくなっても、参照はeaxレジスタに保持されます。

アドレス19の指示は、オプティマイザでさらに多くの作業を実行できることを示しています。時間の許す限り。

于 2010-03-23T02:52:18.923 に答える
2

これに少し異なるスピンを加えるために、コードがILにコンパイルされた後は、プロパティは実際にはメソッドの単なるラッパーであると考えてください。したがって、これの代わりに:

public class nonsensical_calculator
{
    double bleh
    {
        get { return Math.Pow(ar - days, _term); }
    }
    // etc.
}

あなたはこれを持っていました:

public class nonsensical_calculator
{
    double GetBleh()
    {
        return Math.Pow(ar - days, _term);
    }
}

コンパイラがメソッド呼び出しを最適化することを期待しますか?

私はジッターの専門家ではありませんが、ジッターでさえこれを「キャッシュ」するのではないかと思います。あらゆる種類の状態を追跡し、依存フィールドのいずれかが変更されたときにエントリを無効にする必要があります。.NETジッターと同じくらい素晴らしいのですが、それほど賢いとは思いません。メソッドをインライン化することもできますが、通常、パフォーマンス面で大きな違いはありません。

結論として、これらの最適化を行うためにコンパイラやジッターに依存しないでください。また、プロパティゲッターに高価な計算を入れないという一般的な設計ガイドラインに従うことを検討することもできます。これは、呼び出し側には安価であるように見えるためです。

パフォーマンスが必要な場合は、依存フィールドが変更されるたびにこれらの値を事前に計算してください。または、さらに良いことに、EQATEC(無料)やANTSなどのツールを使用してコードのプロファイルを作成し、パフォーマンスコストが実際にどこにあるかを確認します。プロファイリングなしで最適化することは、目隠しをして撮影するようなものです。

于 2010-03-23T02:27:30.783 に答える