简体   繁体   English

Console.WriteLine 慢

[英]Console.WriteLine slow

I run through millions of records and sometimes I have to debug using Console.WriteLine to see what is going on.我浏览了数百万条记录,有时我必须使用Console.WriteLine进行调试以查看发生了什么。

However, Console.WriteLine is very slow, considerably slower than writing to a file.但是, Console.WriteLine非常慢,比写入文件慢得多。

BUT it is very convenient - does anyone know of a way to speed it up?但它非常方便 - 有谁知道加快速度的方法吗?

If it is just for debugging purposes you should use Debug.WriteLine instead.如果只是出于调试目的,则应改用Debug.WriteLine This will most likely be a bit faster than using Console.WriteLine .这很可能比使用Console.WriteLine快一点。

Example例子

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

You can use the OutputDebugString API function to send a string to the debugger.您可以使用OutputDebugString API 函数将字符串发送到调试器。 It doesn't wait for anything to redraw and this is probably the fastest thing you can get without digging into the low-level stuff too much.它不会等待任何东西重新绘制,这可能是您无需过多深入研究低级内容即可获得的最快的东西。 The text you give to this function will go into Visual Studio Output window.您提供给此函数的文本将进入 Visual Studio 输出窗口。

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

Then you just call OutputDebugString("Hello world!");然后你只需调用OutputDebugString("Hello world!");

Do something like this:做这样的事情:

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();

Here is another example: Does Console.WriteLine block?这是另一个示例: Console.WriteLine 是否阻塞?

I recently did a benchmark battery for this on .NET 4.8 .我最近在.NET 4.8上为此做了一个基准电池。 The tests included many of the proposals mentioned on this page, including Async and blocking variants of both BCL and custom code, and then most of those both with and without dedicated threading, and finally scaled across power-of-2 buffer sizes.测试包括本页提到的许多提议,包括 BCL 和自定义代码的Async和阻塞变体,然后是大多数有和没有专用线程的测试,最后扩展到 2 的幂次缓冲区大小。

The fastest method, now used in my own projects, buffers 64K of wide ( Unicode ) characters at a time from .NET directly to the Win32 function WriteConsoleW without copying or even hard-pinning.最快的方法,现在在我自己的项目中使用,一次将 64K 的宽 ( Unicode ) 字符从.NET缓冲到Win32函数WriteConsoleW无需复制甚至硬固定。 Remainders larger than 64K, after filling and flushing one buffer, are also sent directly, and in-situ as well.大于64K的剩余部分,在填充和刷新一个缓冲区后,也直接发送,并且就地发送。 The approach deliberately bypasses the Stream / TextWriter paradigm so it can (obviously enough) provide .NET text that is already Unicode to a (native) Unicode API without all the superfluous memory copying/shuffling and byte[] array allocations required for first "decoding" to a byte stream.该方法故意绕过Stream / TextWriter范式,因此它可以(显然足够)将已经是 Unicode 的.NET 文本提供给(本机)Unicode API,而无需进行首次“解码”所需的所有多余的内存复制/改组和byte[]数组分配" 到字节流。

If there is interest (perhaps because the buffering logic is slightly intricate), I can provide the source for the above;如果有兴趣(可能是因为缓冲逻辑有点复杂),我可以提供上面的来源; it's only about 80 lines.它只有大约 80 行。 However, my tests determined that there's a simpler way to get nearly the same performance, and since it doesn't require any Win32 calls, I'll show this latter technique instead.但是,我的测试确定有一种更简单的方法可以获得几乎相同的性能,并且由于它不需要任何 Win32 调用,因此我将展示后一种技术。

The following is way faster than Console.Write :以下比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(); }
};

Note that this is a buffered writer, so you must call Flush() when you have no more text to write.请注意,这是一个缓冲写入器,因此当您没有更多文本要写入时,您必须调用Flush()

I should also mention that, as shown, technically this code assumes 16-bit Unicode ( UCS-2 , as opposed to UTF-16 ) and thus won't properly handle 4-byte escape surrogates for characters beyond the Basic Multilingual Plane .我还应该提到,如图所示,从技术上讲,此代码假定 16 位 Unicode( UCS-2 ,而不是UTF-16 ),因此无法正确处理Basic Multilingual Plane之外的字符的 4 字节转义代理。 The point hardly seems important given the more extreme limitations on console text display in general, but could perhaps still matter for piping/redirection.考虑到控制台文本显示的更极端限制,这一点似乎并不重要,但对于管道/重定向可能仍然很重要。

Usage:用法:

FastConsole.WriteLine("hello world.");
// etc...
FastConsole.Flush();

On my machine, this gets about 77,000 lines/second (mixed-length) versus only 5,200 lines/sec under identical conditions for normal Console.WriteLine .在我的机器上,这大约为77,000 行/秒(混合长度),而在相同条件下,正常Console.WriteLine仅为5,200行/秒。 That's a factor of almost 15x speedup.这是几乎 15 倍加速的一个因素。

These are controlled comparison results only;这些只是受控的比较结果; note that absolute measurements of console output performance are highly variable, depending on the console window settings and runtime conditions, including size, layout, fonts, DWM clipping, etc.请注意,控制台输出性能的绝对测量值变化很大,这取决于控制台窗口设置和运行时条件,包括大小、布局、字体、DWM 剪辑等。

A little old thread and maybe not exactly what the OP is looking for, but I ran into the same question recently, when processing audio data in real time.一个有点旧的线程,也许不完全是 OP 正在寻找的内容,但我最近在实时处理音频数据时遇到了同样的问题。

I compared Console.WriteLine to Debug.WriteLine with this code and used DebugView as a dos box alternative.我使用此代码Console.WriteLineDebug.WriteLine进行了比较,并使用DebugView作为 dos 框替代方案。 It's only an executable (nothing to install) and can be customized in very neat ways (filters & colors!).它只是一个可执行文件(无需安装)并且可以以非常简洁的方式进行定制(过滤器和颜色!)。 It has no problems with tens of thousands of lines and manages the memory quite well (I could not find any kind of leak, even after days of logging).它在处理数万行时没有问题,并且可以很好地管理内存(即使经过数天的日志记录,我也找不到任何类型的泄漏)。

After doing some testing in different environments (eg: virtual machine, IDE, background processes running, etc) I made the following observations:在不同环境(例如:虚拟机、IDE、后台进程运行等)中进行了一些测试后,我进行了以下观察:

  • Debug is almost always faster Debug几乎总是更快
  • For small bursts of lines (<1000), it's about 10 times faster对于少量的线(<1000),速度大约快 10 倍
  • For larger chunks it seems to converge to about 3x对于较大的块,它似乎收敛到大约 3 倍
  • If the Debug output goes to the IDE, Console is faster :-)如果Debug输出进入 IDE, Console会更快:-)
  • If DebugView is not running, Debug gets even faster如果 DebugView 没有运行, Debug会变得更快
  • For really large amounts of consecutive outputs (>10000), Debug gets slower and Console stays constant.对于非常大量的连续输出(>10000), Debug变慢而Console保持不变。 I presume this is due to the memory, Debug has to allocate and Console does not.我认为这是由于内存,Debug 必须分配而Console没有。
  • Obviously, it makes a difference if DebugView is actually "in-view" or not, as the many gui updates have a significant impact on the overall performance of the system, while Console simply hangs, if visible or not.显然,DebugView 是否真的“在视图中”会有所不同,因为许多 gui 更新对系统的整体性能有重大影响,而 Console 只是挂起,无论是否可见。 But it's hard to put numbers on that one...但是很难给那个数字加上数字......

I did not try multiple threads writing to the Console , as I think this should generally avoided.我没有尝试多线程写入Console ,因为我认为这通常应该避免。 I never had (performance) problems when writing to Debug from multiple threads.从多个线程写入Debug时,我从未遇到过(性能)问题。

If you compile with Release settings, usually all Debug statements are omitted and Trace should produce the same behaviour as Debug.如果您使用 Release 设置进行编译,通常所有Debug语句都会被省略,并且Trace应该产生与 Debug 相同的行为。

I used VS2017 & .Net 4.6.1我使用了VS2017.Net 4.6.1

Sorry for so much code, but I had to tweak it quite a lot to actually measure what I wanted to.抱歉有这么多代码,但我必须对其进行大量调整才能实际衡量我想要的内容。 If you can spot any problems with the code (biases, etc.), please comment.如果您能发现代码的任何问题(偏差等),请发表评论。 I would love to get more precise data for real life systems.我很想为现实生活中的系统获得更精确的数据。

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})");
     }
   }
  };
 }
}

Why Console is slow:为什么控制台很慢:

  • Console output is actually an IO stream that's managed by your operating system.控制台输出实际上是一个由操作系统管理的 IO 流。 Most IO classes (like FileStream ) have async methods but the Console class was never updated so it always blocks the thread when writing.大多数 IO 类(如FileStream )都有异步方法,但Console类从未更新,因此它在写入时总是阻塞线程。

  • Console.WriteLine is backed by SyncTextWriter which uses a global lock to prevent multiple threads from writing partial lines. Console.WriteLineSyncTextWriter支持,它使用全局锁来防止多个线程写入部分行。 This is a major bottleneck that forces all threads to wait for each other to finish the write.这是一个主要瓶颈,它迫使所有线程相互等待完成写入。

  • If the console window is visible on screen then there can be significant slowdown because the window needs to be redrawn before the console output is considered flushed.如果控制台窗口在屏幕上可见,那么速度可能会显着变慢,因为在控制台输出被视为刷新之前需要重新绘制窗口。

Solutions:解决方案:

Wrap the Console stream with a StreamWriter and then use async methods:StreamWriter包装控制台流,然后使用异步方法:

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

You can also set a larger buffer if you need to use sync methods.如果您需要使用同步方法,您还可以设置更大的缓冲区。 The call will occasionally block when the buffer gets full and is flushed to the stream.当缓冲区已满并刷新到流时,该调用偶尔会阻塞。

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

If you want the fastest writes though, you'll need to make your own buffer class that writes to memory and flushes to the console asynchronously in the background using a single thread without locking.但是,如果您想要最快的写入速度,则需要创建自己的缓冲区类,该类使用单线程不锁定地在后台写入内存并异步刷新到控制台。 The new Channel<T> class in .NET Core 2.1 makes this simple and fast. .NET Core 2.1 中Channel<T>使这变得简单而快速。 Plenty of other questions showing that code but comment if you need tips.显示该代码的许多其他问题,但如果您需要提示,请发表评论。

Try using the System.Diagnostics Debug class?尝试使用 System.Diagnostics Debug 类? You can accomplish the same things as using Console.WriteLine.您可以完成与使用 Console.WriteLine 相同的事情。

You can view the available class methods here . 您可以在此处查看可用的类方法

Here is a 7 times faster implementation that bulk-writes to the Console , with a 10 msec delay.这是批量写入Console的 7 倍速度实现,延迟 10 毫秒。 The downside is that you must remember to call Console2.Flush() at the end of the program, otherwise you may lose some output.缺点是一定要记得在程序最后调用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);
    }
}

Usage example:用法示例:

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

Output:输出:

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

Just a little trick I use sometimes: If you remove focus from the Console window by opening another window over it, and leave it until it completes, it won't redraw the window until you refocus, speeding it up significantly.只是我有时使用的一个小技巧:如果您通过在控制台窗口上打开另一个窗口来从控制台窗口中移除焦点,并将其保留直到完成,则在您重新聚焦之前它不会重绘窗口,从而显着加快速度。 Just make sure you have the buffer set up high enough that you can scroll back through all of the output.只要确保您将缓冲区设置得足够高,您就可以回滚所有输出。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM