1. ホーム
  2. c#

[解決済み] Try-catchは私のコードをスピードアップさせるか?

2022-03-18 08:17:19

質問

try-catchの影響をテストするためにいくつかのコードを書きましたが、驚くべき結果が出ています。

static void Main(string[] args)
{
    Thread.CurrentThread.Priority = ThreadPriority.Highest;
    Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.RealTime;

    long start = 0, stop = 0, elapsed = 0;
    double avg = 0.0;

    long temp = Fibo(1);

    for (int i = 1; i < 100000000; i++)
    {
        start = Stopwatch.GetTimestamp();
        temp = Fibo(100);
        stop = Stopwatch.GetTimestamp();

        elapsed = stop - start;
        avg = avg + ((double)elapsed - avg) / i;
    }

    Console.WriteLine("Elapsed: " + avg);
    Console.ReadKey();
}

static long Fibo(int n)
{
    long n1 = 0, n2 = 1, fibo = 0;
    n++;

    for (int i = 1; i < n; i++)
    {
        n1 = n2;
        n2 = fibo;
        fibo = n1 + n2;
    }

    return fibo;
}

私のパソコンでは、これは一貫して0.96前後の値をプリントアウトします。

Fibo()の中のforループをtry-catchブロックで囲むと、こんな感じになります。

static long Fibo(int n)
{
    long n1 = 0, n2 = 1, fibo = 0;
    n++;

    try
    {
        for (int i = 1; i < n; i++)
        {
            n1 = n2;
            n2 = fibo;
            fibo = n1 + n2;
        }
    }
    catch {}

    return fibo;
}

これで、一貫して0.69と出力されるようになりました。しかし、なぜでしょうか?

注:Releaseの設定でコンパイルし、EXEファイルを直接実行しました(Visual Studio外)。

EDIT Jon Skeetの エクセレント 分析 は、try-catch が何らかの原因で、この特定のケースで x86 CLR がより有利な方法で CPU レジスタを使用するようにしていることを示しています(その理由はまだ分かっていないようです)。Jonの発見を確認したところ、x64 CLRにはこの差はなく、x86 CLRよりも高速に動作することがわかりました。また、私は int の代わりに、Fiboメソッド内の long という型に変換したところ、x86のCLRもx64のCLRと同じように高速になりました。


UPDATEです。 この問題はRoslynによって修正されたようです。同じマシン、同じCLRのバージョン -- VS 2013でコンパイルすると上記のような問題が残りますが、VS 2015でコンパイルすると問題が解消されます。

解決方法は?

そのうちの1つは ロスリン スタック使用量の最適化を理解することを専門とするエンジニアがこれを見て、C#コンパイラがローカル変数のストアを生成する方法と、C#コンパイラがローカル変数のストアを生成する方法の間の相互作用に問題があるようだと報告してくれました。 JIT コンパイラは、対応する x86 コードでレジスタのスケジューリングを行います。その結果、ローカルのロードとストアにおいて最適でないコードが生成されます。

JITterがブロックがtry-protected領域にあることを知っている場合、私たち全員に不明な何らかの理由で、問題のあるコード生成経路は回避されます。

これはかなり変ですね。JITterチームにフォローアップして、彼らがこれを修正できるよう、バグを入力してもらえるかどうか確認します。

また、RoslynではC#とVBコンパイラのアルゴリズムを改良して、いつローカルをquot;ephemeralにするか、つまり、起動の間スタック上の特定の場所を確保するのではなく、ただプッシュとポップを繰り返すようにするかということを決定しています。JITterは、ローカルをいつquot;dead"にするかについてより良いヒントを与えれば、レジスタの割り当てやその他をよりうまくできるようになると信じています。

ご指摘ありがとうございます。また、奇妙な動作についてお詫び申し上げます。