1

Was profiling my code and found that the way we are doing colored console text is very expensive (majority of the runtime).

    DateTime dt = DateTime.Now;

    for (int i = 0; i <= 20000; i++)
    {
        ConsoleColor cf = Console.ForegroundColor;
        ConsoleColor cb = Console.BackgroundColor;
        Console.ForegroundColor = ConsoleColor.Red;
        Console.BackgroundColor = ConsoleColor.Blue;
        Console.WriteLine("Hello World");
        Console.ForegroundColor = cf;
        Console.BackgroundColor = cb;
    }

    System.Diagnostics.Debug.WriteLine((DateTime.Now - dt).TotalMilliseconds);

This simple loop takes 2.8 seconds to run on my machine. If I just do the WriteLine, its only 600ms.

Now, before I get troll answers :) asking why I keep setting the color when its hardcoded, THIS IS TEST CODE, in the real code, the foreground and background colors are calculated on a few different factors. That part is irrelevant. This code is just assuming that the color will change and thus saves the originals, changes the colors, then changes it back to the original.

I've also tried using the native SetConsoleTextAttribute method through pinvoke since using ILSpy, it seemed like the Console.xxx methods were doing a lot of extra crap, but I got about the same timings.

SledgeHammer
  • 7,338
  • 6
  • 41
  • 86
  • Changing a property in the console (or doing most anything with the console, really) involves P/Invoking the console process. It would make sense that doing so would introduce some non-trivial overhead. – Abion47 Jan 04 '17 at 00:54
  • @Abion47 I've had times where pinvoke is faster then calling the C# wrappers because they do a lot of stuff to be more bullet proof, whereas with the direct pinvoke call, you can bypass all that. The C# wrappers are probably a few hundred lines of code :). Seems like changing a text color in the console should be trivial. – SledgeHammer Jan 04 '17 at 01:36
  • don't use `DateTime` for time calculations - use `Stopwatch`. – Daniel A. White Jan 04 '17 at 01:45
  • @DanielA.White while in general it is good suggestion it is not going to make much difference for 0.6 vs 2.8 seconds. – Alexei Levenkov Jan 04 '17 at 01:46

2 Answers2

3

Not really. As you've already noted, the properties built in to Console interact with SetConsoleTextAttribute directly, which is the way to set console output properties in Windows.


If ANSI colors are an option, which would be substantial changes to logic you have working with console colors, it is faster. A minimal case

for (int i = 0; i <= 20000; i++)
{
    Console.WriteLine("\x1b[31m\x1b[44mHello World\x1b[39m\x1b[49m");
}

runs in about 1200 ms on my machine, compared to about 7000 ms with the Console.xxxColor properties.

You'll need a compatible shell. Ansicon works.


With ANSI colors, you can additionally batch your output for even more times savings. Even batches of lines of 20 chop the run time above in half.* If you're spamming to the console for whatever reason, that could help significantly.

*On my machine.

jdphenix
  • 15,022
  • 3
  • 41
  • 74
  • Yeah, I thought about using ANSI a few days ago, but then I read that the standard console doesn't support it, so I didn't even try. On the drive home tonight, I thought about writing my own set function that can do the background and foreground in a single call (since SetConsoleTextAttribute can do that), but that'll only help if the user is setting both colors. Probably tomorrow I'm going to try to write a native C++ console app and see how SetConsoleTextAttribute performs there... but the fact that ANSI is so much faster shows there is useless overhead somewhere... – SledgeHammer Jan 04 '17 at 01:51
  • I tried it in native C++ and 20,000 iterations only took 1.24 seconds (vs. 2.8 in C#). As a comparison, just the printf in C++ took 586ms which is comparable to what the C# output alone took. I've never seen pinvoke add that much overhead. – SledgeHammer Jan 04 '17 at 23:39
0

See this answer to "How can I write fast colored output to Console?" and note this solution requires p-invoking of WriteConsoleOutput.

The color information is in the CharInfo struct, using Console.ForegroundColor in the lower 4 bits and BackgroundColor in the upper 4 bits of the short Attributes field.

Here is the constructor I use for the struct:

public CharInfo(char character, ConsoleColor? foreground = null, ConsoleColor? background = null) {
    this.Char = new CharUnion() { UnicodeChar = character };
    this.Attributes = (ushort)((int)(foreground ?? 0) | (((int)(background ?? 0)) << 4));
}

(you will also need to have CharSet=CharSet.Unicode in a couple places in the [StructLayout...] attributes to get Unicode to work)

Do some buffering by constructing an array CharInfo[] sized for the console window, then flush it to screen all at once for immensely-fast* updates!

*Fast relative Console.Write... methods at least. 160x80 can still take over 100ms.

Elaskanator
  • 1,135
  • 10
  • 28