perf(compiler): optimize cloning cursors state (#34332)
On a large compilation unit with big templates, the total time spent in the `PlainCharacterCursor` constructor was 470ms. This commit applies two optimizations to reduce this time: 1. Avoid the object spread operator within the constructor, as the generated `__assign` helper in the emitted UMD bundle (ES5) does not optimize well compared to a hardcoded object literal. This results in a significant performance improvement. Because of the straight-forward object literal, the VM is now much better able to optimize the memory allocations which makes a significant difference as the `PlainCharacterCursor` constructor is called in tight loops. 2. Reduce the number of `CharacterCursor` clones. Although cloning itself is now much faster because of the optimization above, several clone operations were not necessary. Combined, these changes reduce the total time spent in the `PlainCharacterCursor` constructor to just 10ms. PR Close #34332
This commit is contained in:
parent
8c2cbdd385
commit
4945274080
|
@ -242,7 +242,7 @@ class _Tokenizer {
|
||||||
this._currentTokenType = type;
|
this._currentTokenType = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _endToken(parts: string[], end = this._cursor.clone()): Token {
|
private _endToken(parts: string[], end?: CharacterCursor): Token {
|
||||||
if (this._currentTokenStart === null) {
|
if (this._currentTokenStart === null) {
|
||||||
throw new TokenError(
|
throw new TokenError(
|
||||||
'Programming error - attempted to end a token when there was no start to the token',
|
'Programming error - attempted to end a token when there was no start to the token',
|
||||||
|
@ -350,8 +350,7 @@ class _Tokenizer {
|
||||||
private _requireCharCodeUntilFn(predicate: (code: number) => boolean, len: number) {
|
private _requireCharCodeUntilFn(predicate: (code: number) => boolean, len: number) {
|
||||||
const start = this._cursor.clone();
|
const start = this._cursor.clone();
|
||||||
this._attemptCharCodeUntilFn(predicate);
|
this._attemptCharCodeUntilFn(predicate);
|
||||||
const end = this._cursor.clone();
|
if (this._cursor.diff(start) < len) {
|
||||||
if (end.diff(start) < len) {
|
|
||||||
throw this._createError(
|
throw this._createError(
|
||||||
_unexpectedCharacterErrorMsg(this._cursor.peek()), this._cursor.getSpan(start));
|
_unexpectedCharacterErrorMsg(this._cursor.peek()), this._cursor.getSpan(start));
|
||||||
}
|
}
|
||||||
|
@ -821,7 +820,18 @@ class PlainCharacterCursor implements CharacterCursor {
|
||||||
this.file = fileOrCursor.file;
|
this.file = fileOrCursor.file;
|
||||||
this.input = fileOrCursor.input;
|
this.input = fileOrCursor.input;
|
||||||
this.end = fileOrCursor.end;
|
this.end = fileOrCursor.end;
|
||||||
this.state = {...fileOrCursor.state};
|
|
||||||
|
const state = fileOrCursor.state;
|
||||||
|
// Note: avoid using `{...fileOrCursor.state}` here as that has a severe performance penalty.
|
||||||
|
// In ES5 bundles the object spread operator is translated into the `__assign` helper, which
|
||||||
|
// is not optimized by VMs as efficiently as a raw object literal. Since this constructor is
|
||||||
|
// called in tight loops, this difference matters.
|
||||||
|
this.state = {
|
||||||
|
peek: state.peek,
|
||||||
|
offset: state.offset,
|
||||||
|
line: state.line,
|
||||||
|
column: state.column,
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
if (!range) {
|
if (!range) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
@ -851,9 +861,13 @@ class PlainCharacterCursor implements CharacterCursor {
|
||||||
|
|
||||||
getSpan(start?: this, leadingTriviaCodePoints?: number[]): ParseSourceSpan {
|
getSpan(start?: this, leadingTriviaCodePoints?: number[]): ParseSourceSpan {
|
||||||
start = start || this;
|
start = start || this;
|
||||||
|
let cloned = false;
|
||||||
if (leadingTriviaCodePoints) {
|
if (leadingTriviaCodePoints) {
|
||||||
start = start.clone() as this;
|
|
||||||
while (this.diff(start) > 0 && leadingTriviaCodePoints.indexOf(start.peek()) !== -1) {
|
while (this.diff(start) > 0 && leadingTriviaCodePoints.indexOf(start.peek()) !== -1) {
|
||||||
|
if (!cloned) {
|
||||||
|
start = start.clone() as this;
|
||||||
|
cloned = true;
|
||||||
|
}
|
||||||
start.advance();
|
start.advance();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue