14

次のコードを検討してください。

using System;
using System.Diagnostics;

namespace Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            Stopwatch sw = new Stopwatch();

            for (int trial = 0; trial < 4; ++trial)
            {
                sw.Restart();
                loop1();
                Console.WriteLine("loop1() took " + sw.Elapsed);

                sw.Restart();
                loop2();
                Console.WriteLine("loop2() took " + sw.Elapsed);

                sw.Restart();
                loop3();
                Console.WriteLine("loop3() took " + sw.Elapsed);

                // Console.WriteLine(); // <-- Uncomment this and the timings change a LOT!
            }
        }

        static void loop1()
        {
            bool done = false;

            for (int i = 0; i < 100000 && !done; ++i)
            {
                for (int j = 0; j < 100000 && !done; ++j)
                {
                    for (int k = 0; k < 2; ++k)
                    {
                        if (i == 9900)
                        {
                            done = true;
                            break;
                        }
                    }
                }
            }
        }

        static void loop2()
        {
            for (int i = 0; i < 100000; ++i)
            {
                for (int j = 0; j < 100000; ++j)
                {
                    for (int k = 0; k < 2; ++k)
                    {
                        if (i == 9900)
                        {
                            goto exit;
                        }
                    }
                }
            }

        exit: return;
        }

        static void loop3()
        {
            for (int i = 0; i < 100000; ++i)
            {
                for (int j = 0; j < 100000; ++j)
                {
                    for (int k = 0; k < 2; ++k)
                    {
                        if (i == 9900)
                        {
                            k = 2;
                            j = 100000;
                            i = 100000;
                        }
                    }
                }
            }
        }
    }
}

Visual Studio 2010 を使用して Windows 7 x64 でこれの RELEASE x86 ビルドをコンパイルして実行すると、次のタイミングが得られます (Intel Core i7 で実行)。

loop1() took 00:00:01.7935267
loop2() took 00:00:01.4747297
loop3() took 00:00:05.6677592
loop1() took 00:00:01.7654008
loop2() took 00:00:01.4818888
loop3() took 00:00:05.7656440
loop1() took 00:00:01.7990239
loop2() took 00:00:01.5019258
loop3() took 00:00:05.7979425
loop1() took 00:00:01.8356245
loop2() took 00:00:01.5688070
loop3() took 00:00:05.7238753

それ自体が奇妙です - なぜ loop3() は他のループよりもずっと遅いのでしょうか? とにかく、指定された行 (Console.WriteLine()) のコメントを外すと、タイミングは次のようになります。

loop1() took 00:00:01.8229538
loop2() took 00:00:07.8174210
loop3() took 00:00:01.4879274

loop1() took 00:00:01.7691919
loop2() took 00:00:07.4781999
loop3() took 00:00:01.4810248

loop1() took 00:00:01.7749845
loop2() took 00:00:07.5304738
loop3() took 00:00:01.4634904

loop1() took 00:00:01.7521282
loop2() took 00:00:07.6325186
loop3() took 00:00:01.4663219

現在、loop2() ははるかに遅く、loop3() ははるかに高速です。これが一番気になる…

だから私は2つの質問があります:

  1. 他の誰かがこれを再現できますか、そして
  2. もしそうなら、誰かがそれを説明できますか?

[編集] ストップウォッチでこれらのタイミングを確認できること、およびコマンド ラインからテスト プログラムを実行していることを追加する必要があります (これにより、Visual Studio が干渉する可能性を排除できます)。

補遺:

JITTER がループを最適化する可能性を排除するために、次のようにプログラムを変更しました。

using System;
using System.Diagnostics;
using System.Text;

namespace Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(test());
        }

        static string test()
        {
            Stopwatch sw = new Stopwatch();
            int total = 0;
            StringBuilder builder = new StringBuilder();

            for (int trial = 0; trial < 2; ++trial)
            {
                sw.Restart();
                total += loop1();
                builder.AppendLine("loop1() took " + sw.Elapsed);

                sw.Restart();
                total += loop2();
                builder.AppendLine("loop2() took " + sw.Elapsed);

                sw.Restart();
                total += loop3();
                builder.AppendLine("loop3() took " + sw.Elapsed);
                //builder.AppendLine(); // Uncommenting this line makes a big difference!
            }

            builder.AppendLine(total.ToString());

            return builder.ToString();
        }

        static int loop1()
        {
            bool done = false;
            int total = 0;

            for (int i = 0; i < 100000 && !done; ++i)
            {
                for (int j = 0; j < 100000 && !done; ++j)
                {
                    for (int k = 0; k < 2; ++k)
                    {
                        if (i == 9900)
                        {
                            done = true;
                            break;
                        }

                        ++total;
                    }
                }
            }

            return total;
        }

        static int loop2()
        {
            int total = 0;

            for (int i = 0; i < 100000; ++i)
            {
                for (int j = 0; j < 100000; ++j)
                {
                    for (int k = 0; k < 2; ++k)
                    {
                        if (i == 9900)
                        {
                            goto exit;
                        }

                        ++total;
                    }
                }
            }

        exit: return total;
        }

        static int loop3()
        {
            int total = 0;

            for (int i = 0; i < 100000; ++i)
            {
                for (int j = 0; j < 100000; ++j)
                {
                    for (int k = 0; k < 2; ++k)
                    {
                        if (i == 9900)
                        {
                            k = 2;
                            j = 100000;
                            i = 100000;
                        }
                        else
                        {
                            ++total;
                        }
                    }
                }
            }

            return total;
        }
    }
}

今、私の結果は次のとおりです。

builder.AppendLine() はコメントアウトされました:

loop1() took 00:00:06.6509471
loop2() took 00:00:06.7322771
loop3() took 00:00:01.5361389
loop1() took 00:00:06.5746730
loop2() took 00:00:06.7051531
loop3() took 00:00:01.5027345
-1004901888

builder.AppendLine() はコメントアウトされていません:

loop1() took 00:00:06.9444200
loop2() took 00:00:02.8960563
loop3() took 00:00:01.4759535

loop1() took 00:00:06.9036553
loop2() took 00:00:03.1514154
loop3() took 00:00:01.4764172

-1004901888

これを行うときの loop2() タイミングの違いに注意してください。計算しません!

4

1 に答える 1

5

それを正確に再現できます。さらに、次のように分散をなくすことができます。

    private static void Main(string[] args)
    {
        Stopwatch sw = new Stopwatch();

        for (int trial = 0; trial < 4; ++trial)
        {
            sw.Restart();
            Interlocked.MemoryBarrier();
            loop1();
            Interlocked.MemoryBarrier();
            Console.WriteLine("loop1() took " + sw.Elapsed);

            sw.Restart();
            Interlocked.MemoryBarrier();
            loop2();
            Interlocked.MemoryBarrier();
            Console.WriteLine("loop2() took " + sw.Elapsed);

            sw.Restart();
            Interlocked.MemoryBarrier();
            loop3();
            Interlocked.MemoryBarrier();
            Console.WriteLine("loop3() took " + sw.Elapsed);

            // Console.WriteLine(); // <-- Uncomment this and the timings don't change now.
        }
    }

MemoryBarriers を使用して実行すると、どの方法でテストを実行しても、2 番目のパターンが得られます。

loop1() took ~1 sec
loop2() took ~7 secs
loop3() took ~1 sec

MemoryBarrier の定義:

次のようにメモリ アクセスを同期します。現在のスレッドを実行しているプロセッサは、MemoryBarrier への呼び出しの前のメモリ アクセスが、MemoryBarrier への呼び出しに続くメモリ アクセスの後に実行されるような方法で命令を並べ替えることができません。

ループの IL は 2 つのバージョン間で同一であり、MemoryBarrier によって差異が解消されるため、差異は間違いなく最初のパターンの最適化の結果であると結論付けることができると思います...おそらく JITer による...おそらくCPU...よくわかりません。

私の環境は同じですが、VS2012 と .NET 4.5 RTM を使用しています。

于 2012-08-16T11:21:04.857 に答える