terminal
terminal copied to clipboard
The terminal is buffering in continuous printing
Windows Terminal version
1.11.3471.0
Windows build number
10.0.19043.1466
Other Software
No response
Steps to reproduce
// poc.c
#include <stdio.h>
#include <windows.h>
int main(void) {
CONSOLE_SCREEN_BUFFER_INFO csbi;
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
int width = csbi.srWindow.Right - csbi.srWindow.Left + 1;
int height = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
char f[height][width];
memset(f, ' ', height * width);
f[height / 2][width / 2] = '*';
while(1) {
puts(*f);
}
}
>gcc poc.c -o main
>main
Expected Behavior
One "*" character is supposed to appear in the middle of the terminal
Actual Behavior
The terminal is buffering just like I previously mentioned here
Where are you compiling that code and with what?
Where are you compiling that code and with what?
Compiling using gcc
@donno2048 after building poc.c
with clang
on Windows this is the output:
https://user-images.githubusercontent.com/3933920/152261532-3e87d9af-65e6-47a7-b8cd-a614d859496a.mp4
After canceling the test there are some characters left over, not sure what they are:
Is this the output you're seeing too? Thx!
That's odd, using gcc
in cmd I get this (expected behavior):
https://user-images.githubusercontent.com/61805754/152262671-27acfcbe-049c-46d5-86af-7404a949d27f.mp4
And in the terminal this:
https://user-images.githubusercontent.com/61805754/152262642-cbc810f9-043a-4462-b5ea-f385c56bc79e.mp4
Compiling using
gcc
but it doesn't really matter
I suspect it matters a lot. If your compiler are making some default assumption of using windows vs gui builds and libraries etc.
So seeing gcc -v
might be useful. Or if you're doing it from within WSL.
Then clearly the buffer sizes are probably different in CMD vs WT/pwsh. $Host.UI.RawUI
and compare to your cmd.
To me it looks as he 2 last videos are ding the same thing, only that you're lucky that the screen form feed just happen to match up in one, but not the other.
Compiling using
gcc
but it doesn't really matterI suspect it matters a lot. If your compiler are making some default assumption of using windows vs gui builds and libraries etc. So seeing
gcc -v
might be useful. Or if you're doing it from within WSL.
I compile on Windows, on Linux it's working perfectly as I mentioned microsoft/vscode#142001
Then clearly the buffer sizes are probably different in CMD vs WT/pwsh.
$Host.UI.RawUI
and compare to your cmd.To me it looks as he 2 last videos are ding the same thing, only that you're lucky that the screen form feed just happen to match up in one, but not the other.
It isn't "luck" the code is literally reading the height of the console to do that...
The terminal is buffering
I did not compile your example code. Just run simple while()
https://user-images.githubusercontent.com/78153320/152358146-11826aba-be23-46be-9a0b-067e787c0fbb.mp4
@237dmitry So? You're not actually printing the same thing...
The terminal is buffering in continuous printing
You're not actually printing the same thing...
???
I mean I print continuously such that each print covers the entire terminal the excepted behavior is for the terminal to show that string continuously
I suspect this is a duplicate of #11794, in which case it should be improved by PR #11890 (once that's released).
Alright, I don't know how this thread got so off the rails. Gonna minimize half the comments here.
Alas, this didn't get better with #11890. The key here is that OP is just constantly throwing new lines into the buffer, so conpty needs to flush the new line to the terminal side each newline. IIRC I had tried avoiding that in the initial conpty implementation, but it had some bug that forced the immediate flushing. Something about InvalidateCircling
. (I idly wonder if I made that call before deciding that conpty needs to be the same size as the viewport. Maybe that isn't necessary anymore.)
#1173 & #10001 might be a potential solution here. Get ConPTY out of the equation.
OP could probably remove a lot of pain by doing a \e[H
at the start of each frame, by returning the cursor to the top of the screen at each frame. That would prevent them from loading up the scrollback with previous frames. There's still the underlying optimization that could be made here, so let's investigate.
cpp version of OPs code, for my reference
#include <stdio.h>
#include <windows.h>
// This wmain exists for help in writing scratch programs while debugging.
int __cdecl wmain(int /*argc*/, WCHAR* /*argv[]*/)
{
CONSOLE_SCREEN_BUFFER_INFO csbi;
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
int width = csbi.srWindow.Right - csbi.srWindow.Left + 1;
int height = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
char* f = new char [height * width];
memset(f, ' ', height * width);
f[(height / 2) * width + (width / 2)] = '*';
f[height * width - 1] = 0;
while(1) {
puts(f);
}
}
The code posted here has a bug where the null terminator for f
is missing when passed to puts
which expects one.
Use this instead:
fwrite(f, sizeof(char), height * width, stdout);
The immediate flushing of ConPTY is a significant performance issue. vtebench is a really nice tool and it shows that Windows Terminal is pretty alright in almost all areas... except for newline performance, which is 500-900x slower than other popular Terminals. See #10563.
I suspect that fixing the scrolling performance will simultaneously fix this issue more or less. \e[H
, etc. should be used for your CLI application regardless though.
- At the start of your animation hide your cursor with
"\x1b[?25l"
- Use
"\x1b[H"
or"\x1b[1;1H"
to jump back to the top-left of the viewport each frame
Using any type of Unicode special sequence is problematic when running the code from CMD because then it's actually printed...
@donno2048 Maybe I'm misunderstanding you, but if you're referring to my (and other's) suggestion to use "\x1b[H"
:
This isn't unicode, but rather a so called "virtual terminal sequence", or "escape sequence".
You can read more about those here: https://en.wikipedia.org/wiki/ANSI_escape_code#Description
\x1b
is the ESC character in hexadecimal and the same as \033
in octal. Some applications also accept \e
as a synonym.
Modern terminals will parse such escape sequences and not show them on the screen to the user. That way you can for instance tell your terminal to jump anywhere on the screen, or change the text color, with just a bit of hidden text.
In order to use such sequences in CMD you can simply enable the ENABLE_VIRTUAL_TERMINAL_PROCESSING
flag. This flag is always enabled when you run applications in Windows Terminal. Here's some example code: https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#samples
Yeah, I know what an escape sequence I just didn't remember the term 😅 (yeah it isn't Unicode but you know what I mean), in my original issue (microsoft/vscode#142001) I mentioned the possibility to use them to resolve this issue partially, the problem as I said is that when not running in a compatible terminal but rather a "plain" CMD those characters will actually be printed which is problematic
@donno2048 I probably continue to misunderstand you. I'm sorry in advance. 😅 CMD isn't incompatible with escape sequences. It's just disabled by default. I mean maybe you meant to say with "plain CMD" that you explicitly don't want to use escape sequences. Reading your issue on vscode however implies to me that you actually want to use escape sequences, but found that they don't work in CMD, right?
If I'm right, does it work if you try to add this at the very start of your application?
SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT
are enabled by default, but ENABLE_VIRTUAL_TERMINAL_PROCESSING
isn't. This one-liner will enable the flag and allow you to use \x1b[H
in CMD. 🙂
SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
You just blew my mind
@lhecker
Use this instead:
I tried that, but I'm getting weird results. What code are you referring to, can you show the whole thing, and with the compiler options?
Use this instead:
I tried that, but I'm getting weird results. What code are you referring to, can you show the whole thing, and with the compiler options?
The code is the code from the first comment here.
@lhecker
Use this instead:
I tried that, but I'm getting weird results. What code are you referring to, can you show the whole thing, and with the compiler options?
Try this:
#include <stdio.h>
#include <windows.h>
int main(void) {
int height, width;
{
CONSOLE_SCREEN_BUFFER_INFO csbi;
handle_t h = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleMode(h, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
GetConsoleScreenBufferInfo(h, &csbi);
width = csbi.srWindow.Right - csbi.srWindow.Left + 1;
height = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
}
char f[height][width];
memset(f, ' ', height * width);
f[height / 2][width / 2] = '*';
while (TRUE) {
printf("\x1b[H");
fwrite(f, sizeof(char), height * width, stdout);
}
}
A thought I had: Perhaps I could reuse the "buffer but don't flush" from #14677. Flushing is expensive, buffering... less so.
dev/migrie/f/12336-let-it-mellow
has an experimentation into that thought, but it's still only 8MB/s (up from ~6MB/s) compared to the like, 98MB/s of conhost. Though, the passthrough that's in main
(admittedly busted to heck) does get that up to ~70MB/s (up from ~60)
showerthought - does that just queue the frame to get flushed on the render thread, but ultimately, the render thread still ends up blocking on WriteFile. Like, I'm worried that we end up in a place where the flush is still just blocking on I/O instead of properly flushing async.
74181014cfc97a4357b416b2622f73558756357b had an attempt at just moving the WriteFile into Flush
, but we still need to do something faster than that.
The problem with "buffering" linefeeds is that they destroy the conhost buffer, since that's only the size of the visible viewport. Every linefeed you execute without flushing is going to erase one line of content from the top of the viewport. By the time you eventually flush the buffer, there's potentially nothing left to send to the conpty client. What should have been pushed into their scrollback would now be lost.
Oh sorry - to clarify: in the aforementioned branch, when we circle, I'd render the text buffer out to the conpty buffer but not immediately flush the conpty buffer to the end terminal. I'd wait until the normal frame happens to do that. That would get the conpty WriteFile
out of the console I/O thread's hot path (writing text).
That would still preserve the text, because the "frame" still happens before we lose the text. It just gets written to the pipe at a later time.
Now, the real issue is: can we speed writing to the pipe up? Is that slow because pipes are slow? Is it slow because Terminal drains the pipe too slow? Something something xproc calls, etc.
https://github.com/microsoft/terminal/blob/701062649768c4f6bfb5ea77eab9154014995918/src/renderer/vt/invalidate.cpp#L107-L121
hmm. Do we need to pForcePaint
if
- we're circling, AND
- the top line is NOT invalid?
Probably not? That might be a place to speed this up a little. Not sure that'd end up actually optimizing that much out. I'd reckon we're probably outputting to the textbuffer a whole viewport worth of lines faster than a frame anyways
Oh sorry - to clarify: in the aforementioned branch, when we circle, I'd render the text buffer out to the conpty buffer but not immediately flush the conpty buffer to the end terminal.
OK. That makes perfect sense. I'd forgotten there was a conpty buffer.