Console.WriteLine slow
Я запускаю миллионы записей, и иногда мне приходится отлаживать с помощью Console.WriteLine
, чтобы узнать, что происходит.
Однако Console.WriteLine
работает очень медленно, значительно медленнее, чем запись в файл.
НО это очень удобно - кто-нибудь знает способ ускорить его?
Ответы
Ответ 1
Вы можете использовать функцию API OutputDebugString
для отправки строки в отладчик. Он не ждет, чтобы что-то перерисовывало, и это, вероятно, самая быстрая вещь, которую вы можете получить, не слишком сильно вникая в низкоуровневые вещи.
Текст, который вы передаете этой функции, войдет в окно вывода Visual Studio.
[DllImport("kernel32.dll")]
static extern void OutputDebugString(string lpOutputString);
Затем вы просто вызываете OutputDebugString("Hello world!");
Ответ 2
Если это только для целей отладки, вы должны использовать Debug.WriteLine
. Скорее всего, это будет немного быстрее, чем при использовании Console.WriteLine
.
Пример
Debug.WriteLine("There was an error processing the data.");
Ответ 3
Сделайте что-то вроде этого:
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?
Ответ 4
Небольшая старая нить и, возможно, не совсем то, что ищет OP, но я столкнулся с тем же вопросом в последнее время, когда обрабатываю аудиоданные в реальном времени.
Я сравнил Console.WriteLine
с Debug.WriteLine
с этим кодом и использовал DebugView в качестве альтернативы dos. Это только исполняемый файл (ничего не устанавливать) и можно настроить очень аккуратно (фильтры и цвета!). У него нет проблем с десятками тысяч строк и достаточно хорошо управляет памятью (я не мог найти утечки даже после нескольких дней регистрации).
После выполнения некоторых тестов в разных средах (например: виртуальная машина, IDE, фоновые процессы и т.д.) Я сделал следующие замечания:
-
Debug
почти всегда быстрее - Для небольших очередей строк (<1000), это примерно в 10 раз быстрее
- Для больших кусков он, кажется, сходится к примерно 3x
- Если выход
Debug
выходит на IDE, Console
работает быстрее :-) - Если DebugView не работает,
Debug
становится еще быстрее - Для действительно больших объемов последовательных выходов (> 10000)
Debug
становится медленнее, а Console
остается постоянной. Я предполагаю, что это связано с памятью, Debug должен выделять, а Console
- нет. - Очевидно, что имеет значение, если DebugView фактически "in-view" или нет, поскольку многие обновления gui оказывают значительное влияние на общую производительность системы, тогда как консоль просто висит, если она видна или нет. Но трудно поставить цифры на этот...
Я не пробовал писать несколько потоков в Console
, поскольку, как мне кажется, этого обычно не следует. У меня никогда не было проблем с производительностью при записи в Debug
из нескольких потоков.
Если вы скомпилируете с настройками Release, обычно все операторы Debug
опущены, а Trace
должна производить то же поведение, что и Debug.
Я использовал VS2017 и .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})");
}
}
};
}
}
Ответ 5
Попробуйте использовать класс отладки System.Diagnostics? Вы можете сделать то же самое, что и с помощью Console.WriteLine.
Вы можете просмотреть доступные методы класса здесь.
Ответ 6
Почему Консоль работает медленно:
Консольный вывод - это поток ввода-вывода, который используется и перенаправляется вашей операционной системой. Большинство классов ввода-вывода (например, FileStream
) имеют асинхронные методы, но класс Console
никогда не обновлялся и всегда блокирует поток.
Console.WriteLine
поддерживается SyncTextWriter
, который использует глобальную блокировку для предотвращения частичных строк несколькими потоками. Это является основным узким местом и заставляет все потоки ждать, пока не произойдет одиночный блокирующий вызов записи.
Если окно консоли видно на экране, это может привести к значительному замедлению, поскольку необходимо перерисовать окно, прежде чем вывод консоли будет считаться сброшенным.
Решения:
Используйте обертку 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
Если вам нужны самые быстрые записи, вам нужно создать свой собственный буферный класс, который записывает в память и асинхронно сбрасывает в консоль в фоновом режиме, используя один поток без блокировки. новый класс Channel<T>
в .NET Core 2.1 делает это простым и быстрым. Множество других вопросов, показывающих этот код, но комментируйте, если вам нужны советы.
Ответ 7
Вот реализация, которая в 7 раз быстрее, чем массовая запись в Console
, с задержкой 10 мсек. Недостатком является то, что вы должны не забывать вызывать 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 > Line 1
06:27:22.882 > Line 2
...
06:27:22.893 > Line 999
06:27:22.893 > Line 1000
Ответ 8
Недавно я сделал тест батареи для этого на .NET 4.8. Тесты включали в себя многие из предложений, упомянутых на этой странице, включая Async
и варианты блокировки как BCL, так и пользовательского кода, а затем большинство из них как с выделенной, так и без выделенной потоковой передачи, и, наконец, были масштабированы по размеру буфера степени 2.
Самый быстрый метод, который сейчас используется в моих собственных проектах, одновременно буферизует 64 КБ широких (Юникод) символов из .NET непосредственно в функцию Win32 function WriteConsoleW
без копирования или даже жесткого закрепления. Остатки размером более 64 Кб после заполнения и очистки одного буфера также отправляются напрямую и на месте. Этот подход намеренно обходит парадигму Stream
/TextWriter
, поэтому он может (очевидно, достаточно) предоставить текст .NET, который уже является Unicode, для (нативного) 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(); }
};
Обратите внимание, что это писатель с буферизацией, поэтому вы должны вызывать Flush()
, когда у вас больше нет текста для записи.
Я должен также упомянуть, что, как показано, технически этот код предполагает 16-битный Unicode (UCS-2, в отличие от UTF-16) и, следовательно, не будет правильно обрабатывать 4- экранирование байтов для символов за пределами базовой многоязычной плоскости. Вряд ли это кажется важным, учитывая более жесткие ограничения на отображение текста на консоли в целом, но, возможно, все еще может иметь значение для передачи/перенаправления.
Использование:
FastConsole.WriteLine("hello world.");
// etc...
FastConsole.Flush();
На моей машине это дает примерно 77 000 строк/секунду (смешанной длины) по сравнению с 5200 строк/сек при идентичных условиях для обычного Console.WriteLine
. Это почти в 15 раз больше.
Это только результаты контролируемого сравнения; обратите внимание, что абсолютные измерения производительности вывода консоли сильно варьируются, в зависимости от настроек окна консоли и условий выполнения, включая размер, компоновку, шрифты, отсечение DWM и т.д.
Ответ 9
Небольшой трюк, который я использую иногда: если вы удалите фокус из окна консоли, открыв еще одно окно над ним и оставьте его до тех пор, пока он не завершится, он не будет перерисовывать окно до тех пор, пока вы не переориентируете его, значительно ускорив его. Просто убедитесь, что у вас установлен буфер, достаточно высокий, чтобы вы могли прокручивать все выходные данные.