2012-11-13 119 views
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 off),结果比Test1快了约25%,我真的好奇这是为什么。还是写入控制台不能正确地进行基准测试? (在添加更多测试用例时注意到一些奇怪的数据...)

有人能够赐教吗?另外,如果有更好的方法来做到这一点(通过一个字符串并将其中的一部分写入控制台),请随时评论一下。

+1

运行'Test1'最后,看看是否有一个明显的区别。我猜想有一些缓存正在进行。 – keyboardP

+4

我想你需要循环几千次以获得准确的基准? – James

+0

你是否过早地优化? – BAF

回答

5

首先,我同意其他评论,您的测试工具留下了一些不足之处......我重写了它并将其包含在下面。重写后的结果后明确的赢家:

//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'); 
    } 
+0

此线程是否安全?另外,我知道Win10中的操作系统架构已经发生了变化 - 是否还有其他任何高性能的方式? – TheGeekZn

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); 
     } 
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 
+0

你的机器是slooooooow – RAS

+1

我认为这是我的控制台设置。是的,将控制台窗口缩小回80x40会使1和2回到次秒。 – Blorgbeard

2

我也跑这一个10000次,并获得这些结果:

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

与他人观察到的保持这似乎。我很好奇,为什么Test3Test1慢,所以写了第四个测试:

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块,这可能不是你所追求的最终写入所有输出。我会想象这种方法会咀嚼更多的记忆。

相关问题