我需要运行数百万条记录,并且有时需要使用Console.WriteLine
来调试并查看代码执行情况。
然而,Console.WriteLine
非常慢,比写入文件要慢得多。
但是,它非常方便 - 有没有人知道如何加速它的执行速度?
我需要运行数百万条记录,并且有时需要使用Console.WriteLine
来调试并查看代码执行情况。
然而,Console.WriteLine
非常慢,比写入文件要慢得多。
但是,它非常方便 - 有没有人知道如何加速它的执行速度?
Debug.WriteLine
。 这通常比使用 Console.WriteLine
更快。Debug.WriteLine("There was an error processing the data.");
Debug.WriteLine
功能受限。除非计算机安装了IDE,否则它将无法正常工作。 - Hi-AngelOutputDebugString
API函数将字符串发送到调试器。它不会等待任何内容重新绘制,这可能是您在不太深入了解低级别东西的情况下获得的最快速度。[DllImport("kernel32.dll")] static extern void OutputDebugString(string lpOutputString);然后,您只需调用
OutputDebugString("Hello world!");
OutputDebugString
都会立即返回。如果您担心函数调用和实际看到消息之间的时间,那就是另一回事了。 - Oleg Tarasov做类似于这样的事情:
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();
这里是另一个例子:Console.WriteLine是否会阻塞?
最近我在.NET 4.8上进行了一项基准测试。测试包括许多在本页提到的建议,包括Async
和BCL和自定义代码的阻塞变体,然后大部分都有和没有专用线程,并最终在2的幂缓冲区大小范围内扩展。
现在在我的项目中使用的最快方法是,直接从.NET以64K宽的(Unicode)字符为单位缓冲,使用Win32函数WriteConsoleW
发送数据而无需复制或硬固定。填充并冲洗一个缓冲区后大于64K的余数也被直接、原地发送。这种做法故意绕过了Stream
/TextWriter
模型,因此它可以(显然)将.NET文本直接提供给(native) Unicode API,而无需所有超fluous memory copying/shuffling和对于首次“解码”成字节流的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(); }
};
Flush()
。FastConsole.WriteLine("hello world.");
// etc...
FastConsole.Flush();
在我的机器上,在相同条件下,使用普通的Console.WriteLine
只能达到5,200行/秒(混合长度),而使用这种方法可以达到77,000行/秒。这意味着速度提升了近15倍。
这些只是对比结果,需要注意的是控制台输出性能的绝对测量值因控制台窗口设置和运行时条件(包括大小、布局、字体、DWM剪切等)而高度可变。
FastConsole.WriteLine
之后调用FastConsole.Flush
,性能几乎与本机的Console.WriteLine
相同。我想这是可以预料的。性能提升基本上来自于省略每次写入后刷新流。 - Theodor Zoulias为什么控制台输出很慢:
控制台输出实际上是由操作系统管理的IO流。大多数IO类(如FileStream
)都有异步方法,但Console
类从未更新过,因此在写入时始终会阻塞线程。
Console.WriteLine
由SyncTextWriter
支持,它使用全局锁来防止多个线程写入部分行。这是一个主要瓶颈,迫使所有线程等待彼此完成写入。
如果控制台窗口在屏幕上可见,则可能会出现显着的减速,因为在将控制台输出视为已刷新之前,需要重新绘制窗口。
解决方案:
使用StreamWriter
包装控制台流,然后使用异步方法:
var sw = new StreamWriter(Console.OpenStandardOutput());
await sw.WriteLineAsync("...");
如果需要使用同步方法,您还可以设置更大的缓冲区。当缓冲区满时,调用将偶尔被阻塞,并刷新到流中。
// set a buffer size
var sw = new StreamWriter(Console.OpenStandardOutput(), Encoding.UTF8, 8192);
// this write call will block when buffer is full
sw.Write("...")
Channel<T>
类使这变得简单且快速。有很多其他问题显示了该代码,但如果您需要提示,请留言。Task.Run
里面一样吗?我看到 OpenStandardOutput
返回一个提供异步 API 的 Stream
,所以我错了,这确实是异步的。 - E. ShcherboStreamWriter
而不是使用静态的 Console
类。如果你写整行,这仍然会使用全局锁,因此会有争用,这就是我在最后谈论的内容。 - Mani GandhamConsole.WriteLine
和Debug.WriteLine
,并使用DebugView作为一个dos盒子的替代品。它只是一个可执行文件(没有安装),可以以非常整洁的方式进行自定义(过滤器和颜色!)。它没有任何关于日志的泄漏(即使经过数天的记录)。Debug
几乎总是更快的。Debug
输出到IDE,则Console
更快:-)Debug
速度更快。Debug
会变慢,Console
保持不变。我认为这是由于Debug必须分配内存而Console
没有。Console
,因为我认为这通常应该避免。从多个线程写入Debug
时,我从来没有(性能)问题。Debug
语句都会被省略,Trace
应该产生与Debug相同的行为。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})");
}
}
};
}
}
尝试使用System.Diagnostics Debug类?您可以完成与使用Console.WriteLine相同的事情。
Debug.WriteLine
,那不是这样的。这似乎是一个普遍的误解。除非电脑有一个集成开发环境,否则它根本行不通。 - Hi-Angel