7

在我当前的项目中,我必须解析一个字符串,并将其中的一部分写入控制台。在测试如何在没有太多开销的情况下执行此操作时,我发现我测试的一种方法实际上比 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。但这导致花费了大约两倍的时间。我的猜测是它在每次写入后都会刷新,这会使其变慢。所以我尝试了 Test3,使用 StreamWriter(AutoFlush 关闭),它比 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

我也运行了这个 10000 次并得到了这些结果:

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

这似乎与其他人观察到的一致。我很好奇为什么Test3比 慢Test1,所以写了第四个测试:

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 回答
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 回答