Console.WriteLine slow

后端 未结 9 1553
天涯浪人
天涯浪人 2020-11-29 07:35

I run through millions of records and sometimes I have to debug using Console.WriteLine to see what is going on.

However, Console.WriteLine

相关标签:
9条回答
  • 2020-11-29 07:53

    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.

    I compared Console.WriteLine to Debug.WriteLine with this code and used DebugView as a dos box alternative. 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 (e.g.: virtual machine, IDE, background processes running, etc) I made the following observations:

    • Debug is almost always faster
    • For small bursts of lines (<1000), it's about 10 times faster
    • For larger chunks it seems to converge to about 3x
    • If the Debug output goes to the IDE, Console is faster :-)
    • If DebugView is not running, Debug gets even faster
    • For really large amounts of consecutive outputs (>10000), Debug gets slower and Console stays constant. I presume this is due to the memory, Debug has to allocate and Console does not.
    • 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. 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. I never had (performance) problems when writing to Debug from multiple threads.

    If you compile with Release settings, usually all Debug statements are omitted and Trace should produce the same behaviour as Debug.

    I used 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})");
         }
       }
      };
     }
    }
    
    0 讨论(0)
  • 2020-11-29 07:55

    If it is just for debugging purposes you should use Debug.WriteLine instead. This will most likely be a bit faster than using Console.WriteLine.

    Example

    Debug.WriteLine("There was an error processing the data.");
    
    0 讨论(0)
  • 2020-11-29 07:59

    Here is a 7 times faster implementation that bulk-writes to the Console, with a 10 msec delay. The downside is that you must remember to call Console2.Flush() at the end of the program, otherwise you may lose some output.

    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 > Line 2
    ...
    06:27:22.893 > Line 999
    06:27:22.893 > Line 1000

    0 讨论(0)
提交回复
热议问题