Console.WriteLine很慢

时间:2011-03-11 11:14:05

标签: c# performance

我浏览了数百万条记录,有时我必须使用Console.WriteLine进行调试,看看发生了什么。

但是,Console.WriteLine非常慢,比写入文件慢得多。

但它非常方便 - 有没有人知道加速它的方法?

9 个答案:

答案 0 :(得分:12)

如果仅用于调试目的,则应使用Debug.WriteLine。这很可能比使用Console.WriteLine快一点。

示例

Debug.WriteLine("There was an error processing the data.");

答案 1 :(得分:7)

做这样的事情:

public static class QueuedConsole
{
    private static StringBuilder _sb = new StringBuilder();
    private static int _lineCount;

    public void WriteLine(string message)
    {
        _sb.AppendLine(message);
        ++_lineCount;
        if (_lineCount >= 10)
           WriteAll();
    }

    public void WriteAll()
    {
        Console.WriteLine(_sb.ToString());
        _lineCount = 0;
        _sb.Clear();
    }
}

QueuedConsole.WriteLine("This message will not be written directly, but with nine other entries to increase performance.");

//after your operations, end with write all to get the last lines.
QueuedConsole.WriteAll();

这是另一个例子:Does Console.WriteLine block?

答案 2 :(得分:7)

您可以使用OutputDebugString API函数将字符串发送到调试器。它不会等待任何重绘,这可能是你不能过多地挖掘低级别东西所能获得的最快的东西。 您为此函数提供的文本将进入Visual Studio输出窗口。

[DllImport("kernel32.dll")]
static extern void OutputDebugString(string lpOutputString);

然后你只需拨打OutputDebugString("Hello world!");

答案 3 :(得分:3)

一个旧的线程,可能不是OP正在寻找的东西,但我最近遇到了同样的问题,当时实时处理音频数据。

我将Console.WriteLineDebug.WriteLinethis code进行了比较,并使用DebugView作为替代方案。它只是一个可执行文件(无需安装),可以非常简洁的方式进行自定义(过滤器和颜色!)。它没有成千上万行的问题,并且很好地管理内存(即使经过数天的记录,我也找不到任何泄漏)。

在不同的环境中进行一些测试后(例如:虚拟机,IDE,运行的后台进程等),我做了以下观察:

  • Debug几乎总是更快
  • 对于小阵容(<1000),它的速度提高了大约10倍
  • 对于较大的块,它似乎收敛到大约3x
  • 如果Debug输出到IDE,Console更快: - )
  • 如果DebugView未运行,Debug变得更快
  • 对于非常大量的连续输出(&gt; 10000),Debug变慢并且Console保持不变。我认为这是由于内存,Debug必须分配而Console没有。
  • 显然,如果DebugView实际上是&#34; in-view&#34;它会有所不同。或不是,因为许多gui更新对系统的整体性能有重大影响,而Console只是挂起,如果可见或不可见。但是很难将数字放在那个......

我没有尝试多个线程写入Console,因为我认为这通常应该避免。从多个线程写入Debug时,我从未遇到(性能)问题。

如果使用Release设置进行编译,通常会省略所有Debug语句,Trace应该产生与Debug相同的行为。

我使用 VS2017 &amp; .Net 4.6.1

很抱歉这么多代码,但我不得不调整它实际测量我想要的东西。如果您发现代码有任何问题(偏见等),请发表评论。我希望能为现实生活系统获得更精确的数据。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;

namespace Console_vs_Debug {
 class Program {
  class Trial {
   public string name;
   public Action console;
   public Action debug;
   public List < float > consoleMeasuredTimes = new List < float > ();
   public List < float > debugMeasuredTimes = new List < float > ();
  }

  static Stopwatch sw = new Stopwatch();
  private static int repeatLoop = 1000;
  private static int iterations = 2;
  private static int dummy = 0;

  static void Main(string[] args) {
   if (args.Length == 2) {
    repeatLoop = int.Parse(args[0]);
    iterations = int.Parse(args[1]);
   }

   // do some dummy work
   for (int i = 0; i < 100; i++) {
    Console.WriteLine("-");
    Debug.WriteLine("-");
   }

   for (int i = 0; i < iterations; i++) {
    foreach(Trial trial in trials) {
     Thread.Sleep(50);
     sw.Restart();
     for (int r = 0; r < repeatLoop; r++)
      trial.console();
     sw.Stop();
     trial.consoleMeasuredTimes.Add(sw.ElapsedMilliseconds);
     Thread.Sleep(1);
     sw.Restart();
     for (int r = 0; r < repeatLoop; r++)
      trial.debug();
     sw.Stop();
     trial.debugMeasuredTimes.Add(sw.ElapsedMilliseconds);

    }
   }
   Console.WriteLine("---\r\n");
   foreach(Trial trial in trials) {
    var consoleAverage = trial.consoleMeasuredTimes.Average();
    var debugAverage = trial.debugMeasuredTimes.Average();
    Console.WriteLine(trial.name);
    Console.WriteLine($ "    console: {consoleAverage,11:F4}");
    Console.WriteLine($ "      debug: {debugAverage,11:F4}");
    Console.WriteLine($ "{consoleAverage / debugAverage,32:F2} (console/debug)");
    Console.WriteLine();
   }

   Console.WriteLine("all measurements are in milliseconds");
   Console.WriteLine("anykey");
   Console.ReadKey();
  }

  private static List < Trial > trials = new List < Trial > {
   new Trial {
    name = "constant",
     console = delegate {
      Console.WriteLine("A static and constant string");
     },
     debug = delegate {
      Debug.WriteLine("A static and constant string");
     }
   },
   new Trial {
    name = "dynamic",
     console = delegate {
      Console.WriteLine("A dynamically built string (number " + dummy++ + ")");
     },
     debug = delegate {
      Debug.WriteLine("A dynamically built string (number " + dummy++ + ")");
     }
   },
   new Trial {
    name = "interpolated",
     console = delegate {
      Console.WriteLine($ "An interpolated string (number {dummy++,6})");
     },
     debug = delegate {
      Debug.WriteLine($ "An interpolated string (number {dummy++,6})");
     }
   }
  };
 }
}

答案 4 :(得分:1)

尝试使用System.Diagnostics Debug类?您可以使用Console.WriteLine完成相同的操作。

您可以在此处查看可用的课程方法:http://msdn.microsoft.com/en-us/library/system.diagnostics.debug.aspx

答案 5 :(得分:1)

为什么控制台运行缓慢:

  1. 控制台输出是操作系统使用和重定向的IO流。大多数IO类(例如FileStream)都具有异步方法,但是Console类从未更新过,并且始终会阻塞线程。

  2. Console.WriteLineSyncTextWriter支持,后者使用全局锁来防止多个线程写入部分行。这是一个主要的瓶颈,使所有线程等待单个阻塞写调用按顺序进行。

  3. 如果控制台窗口在屏幕上可见,则可能会大大降低运行速度,因为每次更新都需要重新绘制该窗口。

解决方案:

使用包装的StreamWriter,然后使用异步方法写入控制台流:

var sw = new StreamWriter(Console.OpenStandardOutput());
await sw.WriteLineAsync("...");

您还可以设置更大的缓冲区以使用同步方法,并在刷新时偶尔遇到阻塞。

var sw = new StreamWriter(Console.OpenStandardOutput(), Encoding.UTF8, 8192);
sw.Write("...") // this will block for flushing when the buffer size of 8192 is full

但是,如果您想要最快的写入,则需要创建自己的缓冲区类,该缓冲区类可以使用单个线程在不锁定的情况下在后台异步写入内存并刷新到控制台。 new Channel<T> class in .NET Core 2.1使此过程变得简单而快速。还有很多其他问题可以显示该代码,但如果需要提示则可以注释。

答案 6 :(得分:1)

这是实现速度快7倍的实现,它以10毫秒的延迟批量写入Console。缺点是您必须记住在程序末尾调用Console2.Flush(),否则可能会丢失一些输出。

public static class Console2
{
    private static readonly StringBuilder _sb = new StringBuilder();
    private static volatile CancellationTokenSource _cts;
    private static int _count;

    public static void Write(string value)
    {
        lock (_sb) _sb.Append(value);
        ScheduleFlush();
    }
    public static void Write(string format, params object[] args)
    {
        lock (_sb) _sb.AppendFormat(format, args);
        ScheduleFlush();
    }
    public static void WriteLine(string value)
        => Write(value + Environment.NewLine);

    public static void WriteLine(string format, params object[] args)
        => Write(format + Environment.NewLine, args);

    public static void WriteLine()
        => WriteLine("");

    private static void ScheduleFlush()
    {
        _cts?.Cancel();
        var count = Interlocked.Increment(ref _count);
        if (count % 100 == 0) // periodically flush without cancellation
        {
            var fireAndForget = Task.Run(Flush);
        }
        else
        {
            _cts = new CancellationTokenSource();
            var token = _cts.Token;
            var fireAndForget = Task.Run(async () =>
            {
                await Task.Delay(10, token);
                Flush();
            }, token);
        }
    }

    public static void Flush()
    {
        _cts?.Cancel();
        string text;
        lock (_sb)
        {
            if (_sb.Length == 0) return;
            text = _sb.ToString();
            _sb.Clear();
        }
        Console.Write(text);
    }
}

用法示例:

for (int i = 1; i <= 1000; i++)
{
    Console2.WriteLine($"{DateTime.Now:HH:mm:ss.fff} > Line {i}");
}
Console2.Flush();

输出:

  

06:27:22.882>第1行
  06:27:22.882> 2号线
  ...
  06:27:22.893> 999行
  06:27:22.893> 1000行

答案 7 :(得分:1)

我最近在 .NET 4.8 上为此进行了基准测试。测试包括本页上提到的许多建议,包括Async和BCL和自定义代码的阻塞变体,然后是大多数带有和不带有专用线程的建议,最后跨2次幂缓冲区扩展大小。

现在在我自己的项目中使用的最快方法是一次从 .NET 直接缓存到 Win32 的64K宽(Unicode)字符。功能WriteConsoleW,而无需复制甚至固定。在填充并刷新一个缓冲区后,大于64K的余数也将直接发送,也就地发送。该方法故意绕过Stream / TextWriter范例,因此它可以(显然足够)向(本机)Unicode API提供已经是Unicode 的.NET文本。内存复制/改组和{“ {1}}数组分配是第一次”解码“到字节流所必需的。

如果有兴趣(也许是因为缓冲逻辑有点复杂),我可以提供上面的信息;只有大约80行。但是,我的测试确定,有一种更简单的方法可以使几乎达到相同的性能,并且由于它不需要任何Win32调用,因此我将向您展示后一种技术。

以下内容比byte[]

Console.Write

请注意,这是一个缓冲的编写器,因此当您没有更多的文本要写时,必须调用public static class FastConsole { static readonly BufferedStream str; static FastConsole() { Console.OutputEncoding = Encoding.Unicode; // crucial // avoid special "ShadowBuffer" for hard-coded size 0x14000 in 'BufferedStream' str = new BufferedStream(Console.OpenStandardOutput(), 0x15000); } public static void WriteLine(String s) => Write(s + "\r\n"); public static void Write(String s) { // avoid endless 'GetByteCount' dithering in 'Encoding.Unicode.GetBytes(s)' var rgb = new byte[s.Length << 1]; Encoding.Unicode.GetBytes(s, 0, s.Length, rgb, 0); lock (str) // (optional, can omit if appropriate) str.Write(rgb, 0, rgb.Length); } public static void Flush() { lock (str) str.Flush(); } };

我还应该提到,如图所示,从技术上讲,此代码假定使用16位Unicode(UCS-2,而不是UTF-16),因此无法正确处理字符的4字节转义字符超出Basic Multilingual Plane。鉴于控制台文本显示的总体限制更为严格,这一点似乎并不重要,但也许对于管道/重定向仍然很重要。

用法:

Flush()

在我的计算机上,对于正常FastConsole.WriteLine("hello world."); // etc... FastConsole.Flush(); ,在相同条件下,这大约为 77,000行/秒(混合长度),而只有 5,200 行/秒。这是近15倍加速的一个因素。

这些只是受控的比较结果;请注意,控制台输出性能的绝对度量是高度可变的,具体取决于控制台窗口设置和运行时条件,包括大小,布局,字体,DWM裁剪等。

答案 8 :(得分:0)

我有时会使用一个小技巧:如果你通过打开另一个窗口从控制台窗口移除焦点,并将其保留到完成,它将不会重绘窗口,直到你重新聚焦,显着加快速度。只需确保缓冲区设置得足够高,以便可以向后滚动所有输出。