-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Optimize parsing of OSC_STRING to minimize string concatenation. #1822
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sweet thx, did a quick benchmark - OSC now runs 25% faster. More below.
src/EscapeSequenceParser.ts
Outdated
transition = (code < 0xa0 | ||
? (table[currentState << 8 | code]) | ||
: currentState === ParserState.OSC_STRING | ||
? (ParserAction.OSC_PUT << 4) | ParserState.OSC_STRING |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm - can OSC contain higher chars at all? vt100.net does not cover this as it does not cover unicode at all.
If we have to handle unicode here maybe extend the error state? Note that I am not very fond of the way the parser handles unicode atm, maybe we even find a better way than misusing the error state.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can OSC contain higher chars at all? vt100.net does not cover this as it does not cover unicode at all.
More relevant is what xterm does. The code is a bit hard to read, but I think we're safe treating at least codes above 0xFF as printable. Codes between 0xA0 and 0xFF are probably ok too, though xterm's parse tables are indexed by 8-bit (Latin-1?) codes, not 7-bit ANSII. (Other modern well-maintained terminal emulators such as gnome-terminal are probably also worth looking at.)
(I assume you know xterm.js does a pretty poor job with vttest, so if we're concerned about compatibility there are higher priorities.)
A quick hack for characters above 0x9f is to use some suitable printable character instead:
table[currentState << 8 | (code < 0xa0 ? code : 97)]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well the error state treats any code >0x9f as printable but limits printables to certain states. This could be extended by the OSC_STRING state.
About vttest - yeah, we have several issues open regarding this (e.g. #1434 this is a really wicked one), also we have test cases that compare the buffer state with xterm (https://github.com/xtermjs/xterm.js/tree/master/fixtures/escape_sequence_files many are still disabled since xterm.js does not the right thing yet). Basically vttest compliance boils down to InputHandler
doing things slightly different still.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps a special pseudo-character for code above 0x9f:
const NON_ASCII_PRINTABLE = 0xA0;
table.add(NON_ASCII_PRINTABLE, ParserState.OSC_STRING, ParserAction.OSC_PUT, ParserState.OSC_STRING);
and then:
table[currentState << 8 | (code < 0xa0 ? code : NON_ASCII_PRINTABLE)]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yepp I like that idea 👍
Maybe we can even apply this to the other unicode aware states later on, would greatly simplify the error state (and restore its original purpose lol).
@@ -517,7 +517,16 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP | |||
osc = ''; | |||
break; | |||
case ParserAction.OSC_PUT: | |||
osc += data.charAt(i); | |||
for (let j = i + 1; ; j++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we simplify the check by doing an including check against printables (range check? and maybe unicode if we have to cover those)? This would avoid the table lookup which is kinda costly.
Note an including check does not have to cover all chars, we might get away with the most common. All others will still be handled by the switch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about this:
case ParserAction.OSC_PUT:
for (let j = i + 1; ; j++) {
if (j >= l
|| (code = data.charCodeAt(j)) <= 0x20
|| (code >= 0x7f && code <= 0x9f)) {
osc += data.substring(i, j);
i = j - 1;
break;
}
}
break;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh wait this is not quite correct, put is defined as 0x20 - 0x7f, seems 0x20 and 0x7f should be added to the string. I wonder about 0x7f (DEL) though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely should be < 0x20
rather than <= 0x20
.
Less sure about code >= 0x7f
vs code > 0x7f
. Clearly 0x7F isn't "printable". One might argue that while it should be allowed in an OSC string it is sufficiently dubious that we don't want to hardwire it the code, but instead have it be controlled by the transition table. But as long as we don't have a mechanism for overriding the transition table (and are not planning one) we might as well go with the more efficient code > 0x7f
. I guess.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I think we should simply add it for now (S.E.P. for the parser 😄). DEC VTs handled this differently for the GROUND state as decribed here https://vt100.net/emu/dec_ansi_parser#STGRO.
The transition table can be overridden as ctor argument. But this is currently not exposed, we had no reason so far to do so. In theory the VT models would need slightly different tables (DEC changed alot for >VT300), the table currently resembles VT500 generation (thus not perfectly VT100 compliant).
I am not very happy with the way the parser handles higher chars atm in the error state. Feel free to come up with a better approach. Also I think to do the inner looping in the switch state is the better approach than my shortcuts I did for PRINT and CSI_PARAM at the loop top (the latter even doesnt gain much). Maybe we should refactor this as well (would also cut alot of instructions from other states, that have to test against Btw you can test the parser perf with https://github.com/xtermjs/xterm-benchmark, just clone it next to the xterm.js folder an run:
|
Few more thoughts on OSC in general: |
I added a commit with the changes we discussed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 LGTM, just a minor fix needed, see below. Gonna test it tom.
src/EscapeSequenceParser.ts
Outdated
@@ -79,7 +80,7 @@ export const VT500_TRANSITION_TABLE = (function (): TransitionTable { | |||
const states: number[] = r(ParserState.GROUND, ParserState.DCS_PASSTHROUGH + 1); | |||
let state: any; | |||
|
|||
// table with default transition [any] --> DEFAULT_TRANSITION | |||
// table with default transition | |||
for (state in states) { | |||
// NOTE: table lookup is capped at 0xa0 in parse to keep the table small | |||
for (let code = 0; code < 160; ++code) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changing the transition table access in parse
to code <= 0xa0
would need to include 0xa0
here as well, otherwise the other unicode aware states will be handled with IGNORE/GROUND transition.
@PerBothner Created an issue #1823 to track the ideas we discussed above for other parser parts. |
A lot of tests are broken:
https://dev.azure.com/xtermjs/xterm.js/_build/results?buildId=877 |
@Tyriar Should be fixed now. |
@PerBothner Thx alot for the PR, works like charm and throughput went from 18 to 27 MB/s 👍. Esp. bigger payloads will benefit from this (only tested with a 2 char payload string). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @PerBothner 👍
This avoids string concatenation for for each character of OSC data.
This updated version of the patch (compared to an earlier suggestion in #1813 (comment)) also handles the case when the OSC command is split across multiple calls to
parse
, which is more likely for really long requests).(The handling of characters above 0x9f is a bit clunky - I think it would be better to use a magic fake character code representing characters above 0x9f, and look up that character in the
table
- however, that is a bigger and less local change.)