Skip to content

Commit 32553b4

Browse files
authored
Merge pull request #5825 from homanp/perf/csi-fast-path
perf: CSI fast-path (+42% throughput)
2 parents 0911688 + 6c2465d commit 32553b4

1 file changed

Lines changed: 80 additions & 21 deletions

File tree

src/common/parser/EscapeSequenceParser.ts

Lines changed: 80 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,10 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
506506
* - OSC_STRING:OSC_PUT
507507
* - DCS_PASSTHROUGH:DCS_PUT
508508
*
509+
* Additionally the following fast paths exist before the table lookup:
510+
* - EXE bytes < 0x18 in non-payload states (avoids table lookup entirely)
511+
* - 7-bit CSI sequences without intermediates (ESC [ params final)
512+
*
509513
* Note on asynchronous handler support:
510514
* Any handler returning a promise will be treated as asynchronous.
511515
* To keep the in-band blocking working for async handlers, `parse` pauses execution,
@@ -649,34 +653,89 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
649653
for (let i = start; i < length; ++i) {
650654
code = data[i];
651655

656+
// EXE fast-path: common control bytes (0x00-0x17) in non-payload states
657+
if (code < 0x18 && this.currentState <= ParserState.CSI_IGNORE) {
658+
if (this._executeHandlers[code]) this._executeHandlers[code]();
659+
else this._executeHandlerFb(code);
660+
this.precedingJoinState = 0;
661+
continue;
662+
}
663+
664+
// CSI fast-path: collapse ESC [ into a single entry, parse params+final in a tight loop
665+
if (code === 0x1b
666+
&& this.currentState < ParserState.OSC_STRING
667+
&& i + 2 < length && data[i + 1] === 0x5b
668+
) {
669+
this._params.reset();
670+
this._params.addParam(0); // ZDM
671+
this._collect = 0;
672+
let k = i + 2;
673+
let ch = data[k];
674+
if (ch >= 0x3c && ch <= 0x3f) {
675+
this._collect = ch;
676+
k++;
677+
}
678+
let csiDone = false;
679+
for (; k < length; k++) {
680+
ch = data[k];
681+
if (ch >= 0x30 && ch <= 0x39) {
682+
this._params.addDigit(ch - 48);
683+
} else if (ch === 0x3b) {
684+
this._params.addParam(0);
685+
} else if (ch === 0x3a) {
686+
this._params.addSubParam(-1);
687+
} else if (ch >= 0x40 && ch <= 0x7e) {
688+
const handlers = this._csiHandlers[this._collect << 8 | ch];
689+
let j = handlers ? handlers.length - 1 : -1;
690+
for (; j >= 0; j--) {
691+
handlerResult = handlers[j](this._params);
692+
if (handlerResult === true) {
693+
break;
694+
} else if (handlerResult instanceof Promise) {
695+
transition = ParserAction.CSI_DISPATCH << TableAccess.TRANSITION_ACTION_SHIFT | ParserState.GROUND;
696+
this._preserveStack(ParserStackType.CSI, handlers, j, transition, k);
697+
return handlerResult;
698+
}
699+
}
700+
if (j < 0) {
701+
this._csiHandlerFb(this._collect << 8 | ch, this._params);
702+
}
703+
this.precedingJoinState = 0;
704+
i = k;
705+
this.currentState = ParserState.GROUND;
706+
csiDone = true;
707+
break;
708+
} else {
709+
break;
710+
}
711+
}
712+
if (!csiDone) {
713+
i = k - 1;
714+
this.currentState = ParserState.CSI_PARAM;
715+
}
716+
continue;
717+
}
718+
652719
// normal transition & action lookup
653720
transition = this._transitions.table[this.currentState << TableAccess.INDEX_STATE_SHIFT | (code < 0xa0 ? code : NON_ASCII_PRINTABLE)];
654721
switch (transition >> TableAccess.TRANSITION_ACTION_SHIFT) {
655722
case ParserAction.PRINT:
656-
// read ahead with loop unrolling
657723
// Note: 0x20 (SP) is included, 0x7F (DEL) is excluded
658-
for (let j = i + 1; ; ++j) {
659-
if (j >= length || (code = data[j]) < 0x20 || (code > 0x7e && code < NON_ASCII_PRINTABLE)) {
660-
this._printHandler(data, i, j);
661-
i = j - 1;
662-
break;
663-
}
664-
if (++j >= length || (code = data[j]) < 0x20 || (code > 0x7e && code < NON_ASCII_PRINTABLE)) {
665-
this._printHandler(data, i, j);
666-
i = j - 1;
667-
break;
668-
}
669-
if (++j >= length || (code = data[j]) < 0x20 || (code > 0x7e && code < NON_ASCII_PRINTABLE)) {
670-
this._printHandler(data, i, j);
671-
i = j - 1;
672-
break;
673-
}
674-
if (++j >= length || (code = data[j]) < 0x20 || (code > 0x7e && code < NON_ASCII_PRINTABLE)) {
675-
this._printHandler(data, i, j);
676-
i = j - 1;
677-
break;
724+
let c = i;
725+
const l4 = length - 4;
726+
while (c < l4
727+
&& data[++c] >= 0x20 && (data[c] <= 0x7e || data[c] >= NON_ASCII_PRINTABLE)
728+
&& data[++c] >= 0x20 && (data[c] <= 0x7e || data[c] >= NON_ASCII_PRINTABLE)
729+
&& data[++c] >= 0x20 && (data[c] <= 0x7e || data[c] >= NON_ASCII_PRINTABLE)
730+
&& data[++c] >= 0x20 && (data[c] <= 0x7e || data[c] >= NON_ASCII_PRINTABLE)
731+
) {}
732+
if (c >= l4) {
733+
while (c < length && data[c] >= 0x20 && (data[c] <= 0x7e || data[c] >= NON_ASCII_PRINTABLE)) {
734+
c++;
678735
}
679736
}
737+
this._printHandler(data, i, c);
738+
i = c - 1;
680739
break;
681740
case ParserAction.EXECUTE:
682741
if (this._executeHandlers[code]) this._executeHandlers[code]();

0 commit comments

Comments
 (0)