Подтвердить что ты не робот

Console.WriteLine slow

Я запускаю миллионы записей, и иногда мне приходится отлаживать с помощью Console.WriteLine, чтобы узнать, что происходит.

Однако Console.WriteLine работает очень медленно, значительно медленнее, чем запись в файл.

НО это очень удобно - кто-нибудь знает способ ускорить его?

4b9b3361

Ответ 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})");
     }
   }
  };
 }
}

Ответ 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

Небольшой трюк, который я использую иногда: если вы удалите фокус из окна консоли, открыв еще одно окно над ним и оставьте его до тех пор, пока он не завершится, он не будет перерисовывать окно до тех пор, пока вы не переориентируете его, значительно ускорив его. Просто убедитесь, что у вас установлен буфер, достаточно высокий, чтобы вы могли прокручивать все выходные данные.