Console.WriteLine很慢

Man*_*oon 24 c# performance

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

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

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

Fil*_*erg 13

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

Debug.WriteLine

  • Filip是对的,Debug.WriteLine更快.但它不仅仅"快一点"......它的速度更快了. (7认同)
  • 为什么屏幕重绘的时间比Console.WriteLIne长? (2认同)

Ole*_*sov 8

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

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

然后你就打电话 OutputDebugString("Hello world!");


jga*_*fin 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();
Run Code Online (Sandbox Code Playgroud)

这是另一个例子:Console.WriteLine是否阻止?

  • 创建一个类可能会更好,然后析构函数可以打印剩余的缓冲区,如果 prog. 崩溃。 (2认同)

Man*_*ham 6

为什么控制台很慢:

  • 控制台输出实际上是由操作系统管理的 IO 流。大多数 IO 类(如FileStream)都有异步方法,但该类Console从未更新,因此在写入时它总是阻塞线程。

  • Console.WriteLine支持SyncTextWriter使用全局锁来防止多个线程写入部分行。这是一个主要瓶颈,迫使所有线程相互等待对方完成写入。

  • 如果控制台窗口在屏幕上可见,则速度可能会显着减慢,因为在控制台输出被视为刷新之前需要重新绘制窗口。

解决方案:

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

var sw = new StreamWriter(Console.OpenStandardOutput());
await sw.WriteLineAsync("...");
Run Code Online (Sandbox Code Playgroud)

如果需要使用同步方法,您还可以设置更大的缓冲区。当缓冲区已满并刷新到流时,调用偶尔会阻塞。

// set a buffer size
var sw = new StreamWriter(Console.OpenStandardOutput(), Encoding.UTF8, 8192);
// this write call will block when buffer is full
sw.Write("...")
Run Code Online (Sandbox Code Playgroud)

如果您想要最快的写入速度,则需要创建自己的缓冲区类,该缓冲区类使用单个线程在后台异步写入内存并刷新到控制台,而无需锁定。.NET Core 2.1 中的Channel<T>类使这一切变得简单而快速。很多其他问题显示了该代码,但如果您需要提示,请发表评论。


Gle*_*den 5

我最近在.NET 4.8上为此做了一个基准电池。测试包括本页提到的许多提议,包括AsyncBCL 和自定义代码的阻塞变体,然后是大多数有和没有专用线程的测试,最后扩展到 2 的幂次缓冲区大小。

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

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

以下方法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(); }
};
Run Code Online (Sandbox Code Playgroud)

请注意,这是一个缓冲写入器,因此您必须Flush()在没有更多文本可写入时调用。

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

用法:

FastConsole.WriteLine("hello world.");
// etc...
FastConsole.Flush();
Run Code Online (Sandbox Code Playgroud)

在我的机器,这得到约77000行/秒(混合长度)相对于仅5200为正常相同的条件下行/秒Console.WriteLine。这是几乎 15 倍加速的一个因素。

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