7

私の現在のプロジェクトでは、文字列を解析し、その一部をコンソールに書き込む必要があります。あまりオーバーヘッドをかけずにこれを行う方法をテストしているときに、テストしていた1つの方法が、実際にはConsole.WriteLineよりも高速であることがわかりました。これは少し混乱します。

これはベンチマークを行うための適切な方法ではないことは承知していますが、通常は「これはこれよりも高速です」という大まかな方法​​で問題ありません。これは、数回実行した後でわかります。

static void Main(string[] args)
{
    var timer = new Stopwatch();

    timer.Restart();
    Test1("just a little test string.");
    timer.Stop();
    Console.WriteLine(timer.Elapsed);

    timer.Restart();
    Test2("just a little test string.");
    timer.Stop();
    Console.WriteLine(timer.Elapsed);

    timer.Restart();
    Test3("just a little test string.");
    timer.Stop();
    Console.WriteLine(timer.Elapsed);
}

static void Test1(string str)
{
    Console.WriteLine(str);
}

static void Test2(string str)
{
    foreach (var c in str)
        Console.Write(c);
    Console.Write('\n');
}

static void Test3(string str)
{
    using (var stream = new StreamWriter(Console.OpenStandardOutput()))
    {
        foreach (var c in str)
            stream.Write(c);
        stream.Write('\n');
    }
}

ご覧のとおり、Test1はConsole.WriteLineを使用しています。私が最初に考えたのは、文字ごとにWriteを呼び出すことでした。Test2を参照してください。しかし、これにより、約2倍の時間がかかりました。私の推測では、書き込みのたびにフラッシュするため、処理が遅くなります。そこで、StreamWriter(AutoFlush off)を使用してTest3を試しましたが、その結果、Test1よりも約25%速くなりました。なぜそうなのか、本当に興味があります。それとも、コンソールへの書き込みを適切にベンチマークできないということですか?(テストケースを追加すると、奇妙なデータに気づきました...)

誰かが私を啓発できますか?

また、これを行うためのより良い方法がある場合(文字列を調べて、その一部のみをコンソールに書き込む)、コメントしてください。

4

4 に答える 4

6

最初に、あなたのテストハーネスが何かを残しているという他のコメントに同意します...私はそれを書き直して以下に含めました。書き直した後の結果は、明確な勝者を投稿します。

//Test 1 = 00:00:03.7066514
//Test 2 = 00:00:24.6765818
//Test 3 = 00:00:00.8609692

このことから、バッファリングされたストリームライターは25%以上高速であることが正しいです。バッファリングされているという理由だけで高速になります。内部的には、StreamWriterの実装は約1〜4kbのデフォルトのバッファサイズを使用します(ストリームタイプによって異なります)。8バイトのバッファ(許可されている最小のバッファ)を使用してStreamWriterを構築すると、パフォーマンスの向上のほとんどが失われます。これは、各書き込みの後にFlush()呼び出しを使用して確認することもできます。

上記の数値を取得するために書き直されたテストは次のとおりです。

    private static StreamWriter stdout = new StreamWriter(Console.OpenStandardOutput());
    static void Main(string[] args)
    {
        Action<string>[] tests = new Action<string>[] { Test1, Test2, Test3 };
        TimeSpan[] timming = new TimeSpan[tests.Length];

        // Repeat the entire sequence of tests many times to accumulate the result
        for (int i = 0; i < 100; i++)
        {
            for( int itest =0; itest < tests.Length; itest++)
            {
                string text = String.Format("just a little test string, test = {0}, iteration = {1}", itest, i);
                Action<string> thisTest = tests[itest];

                //Clear the console so that each test begins from the same state
                Console.Clear();
                var timer = Stopwatch.StartNew();
                //Repeat the test many times, if this was not using the console 
                //I would use a much higher number, say 10,000
                for (int j = 0; j < 100; j++)
                    thisTest(text);
                timer.Stop();
                //Accumulate the result, but ignore the first run
                if (i != 0)
                    timming[itest] += timer.Elapsed;

                //Depending on what you are benchmarking you may need to force GC here
            }
        }

        //Now print the results we have collected
        Console.Clear();
        for (int itest = 0; itest < tests.Length; itest++)
            Console.WriteLine("Test {0} = {1}", itest + 1, timming[itest]);
        Console.ReadLine();
    }

    static void Test1(string str)
    {
        Console.WriteLine(str);
    }

    static void Test2(string str)
    {
        foreach (var c in str)
            Console.Write(c);
        Console.Write('\n');
    }

    static void Test3(string str)
    {
        foreach (var c in str)
            stdout.Write(c);
        stdout.Write('\n');
    }
于 2012-11-14T00:22:08.080 に答える
3

テストをそれぞれ10000回実行しましたが、私のマシンでの結果は次のとおりです。

test1 - 0.6164241  
test2 - 8.8143273    
test3 - 0.9537039  

これは私が使用したスクリプトです:

 static void Main(string[] args)
        {
            Test1("just a little test string.");     // warm up
            GC.Collect();  // compact Heap
            GC.WaitForPendingFinalizers(); // and wait for the finalizer queue to empty
            Stopwatch timer = new Stopwatch();
            timer.Start();
            for (int i = 0; i < 10000; i++)
            {
                Test1("just a little test string.");
            }
            timer.Stop();
            Console.WriteLine(timer.Elapsed);
        }
于 2012-11-13T23:19:35.237 に答える
2

各テストを1000回実行するようにコードを変更しました。

    static void Main(string[] args) {
        var timer = new Stopwatch();

        timer.Restart();
        for (int i = 0; i < 1000; i++)
            Test1("just a little test string.");
        timer.Stop();
        TimeSpan elapsed1 = timer.Elapsed;

        timer.Restart();
        for (int i = 0; i < 1000; i++)
            Test2("just a little test string.");
        timer.Stop();
        TimeSpan elapsed2 = timer.Elapsed;

        timer.Restart();
        for (int i = 0; i < 1000; i++)
            Test3("just a little test string.");
        timer.Stop();
        TimeSpan elapsed3 = timer.Elapsed;

        Console.WriteLine(elapsed1);
        Console.WriteLine(elapsed2);
        Console.WriteLine(elapsed3);

        Console.Read();
    }

私の出力:

00:00:05.2172738
00:00:09.3893525
00:00:05.9624869
于 2012-11-13T23:27:25.263 に答える
2

また、これを10000回実行して、次の結果を得ました。

00:00:00.6947374
00:00:09.6185047
00:00:00.8006468

これは他の人が観察したことと一致しているようです。Test3なぜより遅いのか興味があっTest1たので、4番目のテストを書きました:

timer.Start();
using (var stream = new StreamWriter(Console.OpenStandardOutput()))
{
    for (int i = 0; i < testSize; i++)
    {
        Test4("just a little test string.", stream);
    }
}
timer.Stop();

これは、テストごとにストリームを再利用するため、毎回再作成するオーバーヘッドを回避できます。結果:

00:00:00.4090399

これは最速ですが、usingブロックの最後にすべての出力を書き込みます。これは、目的の出力ではない場合があります。このアプローチは、より多くのメモリをかみ砕くことになると思います。

于 2012-11-13T23:39:13.650 に答える