Skip to content
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

Bracket pair parsing runs twice on keypress #163304

Closed
Tyriar opened this issue Oct 11, 2022 · 5 comments · Fixed by #165916
Closed

Bracket pair parsing runs twice on keypress #163304

Tyriar opened this issue Oct 11, 2022 · 5 comments · Fixed by #165916
Assignees
Labels
editor-bracket-matching Editor brace matching feature-request Request for new features or functionality insiders-released Patch has been released in VS Code Insiders perf verification-needed Verification of issue is requested verified Verification succeeded
Milestone

Comments

@Tyriar
Copy link
Member

Tyriar commented Oct 11, 2022

Part of #161622

Bracket pair parsing appears to happen twice on a single keypress as it happens when content changes and when tokenization happens. Typically on simple input "cheap" tokenization runs in the same task:

image

Consider using a microtask to execute the parsing at the end of the task a single time (which may get deferred to later depending on the outcome of #163235), rather than eagerly during the content changed event. I haven't looked much at the code but could we do bracket matching more incrementally so it's not taking several milliseconds?

The impact of this running twice is that input latency is pushed out several milliseconds on lower end/average hardware.

@Tyriar Tyriar added bug Issue identified by VS Code Team member as probable bug editor-bracket-matching Editor brace matching perf labels Oct 11, 2022
@Tyriar Tyriar added this to the October 2022 milestone Oct 11, 2022
@hediet
Copy link
Member

hediet commented Oct 12, 2022

On which document did you try this out?

image

The entire thing is 5.98ms, so it is 2% for me.

Also, it is by design that it reacts on both content changes and token changes - maybe it can be optimized, but I wouldn't call this a "bug".

I haven't looked much at the code but could we do bracket matching more incrementally so it's not taking several milliseconds?

Here is a description of the algorithm:
https://code.visualstudio.com/blogs/2021/09/29/bracket-pair-colorization

This section is about how incrementality is achieved:
https://code.visualstudio.com/blogs/2021/09/29/bracket-pair-colorization#_incremental-updates

Maybe there are some cheap optimizations that I missed though (e.g. getting rid of recursion).

@hediet hediet added feature-request Request for new features or functionality and removed bug Issue identified by VS Code Team member as probable bug labels Oct 12, 2022
@Tyriar
Copy link
Member Author

Tyriar commented Oct 12, 2022

On which document did you try this out?

I was mostly using InputHandler.ts from xterm.js or terminalInstance.ts from vscode yesterday.

The entire thing is 5.98ms, so it is 2% for me.

This is on your fast machine that is likely quite a ways off the average user (and the 50% below the average). I think for that screenshot I was using 4x CPU throttling on my main computer (Windows 11) to emphasise slow parts.

Also, it is by design that it reacts on both content changes and token changes - maybe it can be optimized, but I wouldn't call this a "bug".

Maybe I'm misunderstanding the incremental parsing, but if it's could do in less time what it does in content change after tokenization, it should be changed. To clarify, this is how I propose you solve it:

onContentChange(() => this._queueParse());
onTokenChange(() => this._queueParse());

_queued = false
_queueParse() {
  if (!this._queued) {
    queueMicrotask(() => this._parse());
    this.queued = true;
  }
}

That will make _parse run a single time at the end of the task (ie. before any rendering occurs).

Here is a description of the algorithm:
https://code.visualstudio.com/blogs/2021/09/29/bracket-pair-colorization

This section is about how incrementality is achieved:
https://code.visualstudio.com/blogs/2021/09/29/bracket-pair-colorization#_incremental-updates

Maybe there are some cheap optimizations that I missed though (e.g. getting rid of recursion).

What confused me a little is why it wasn't essentially instant when I was just typing a non-bracket character. I didn't look into how the algorithm works or dig very deep into the code as I'm sure you know what you're doing there, I'm more interested in ensuring it doesn't waste time running twice in a single task and whether this can be deferred to shortly after text is rendered.

@Tyriar Tyriar modified the milestones: October 2022, November 2022 Oct 12, 2022
@Tyriar
Copy link
Member Author

Tyriar commented Oct 12, 2022

Outcomes from our meeting:

  • We probably can't defer this stuff as character changes next to brackets will shift the bracket decorations to the character to the left or right for example
  • We could merge content and token change handlers into a single handler, we would just need to merge the ranges such that they don't overlap here:

public handleDidChangeTokens({ ranges }: IModelTokensChangedEvent): void {
const edits = ranges.map(r =>
new TextEditInfo(
toLength(r.fromLineNumber - 1, 0),
toLength(r.toLineNumber, 0),
toLength(r.toLineNumber - r.fromLineNumber + 1, 0)
)
);
this.astWithTokens = this.parseDocumentFromTextBuffer(edits, this.astWithTokens, false);
if (!this.initialAstWithoutTokens) {
this.didChangeEmitter.fire();
}
}
public handleContentChanged(change: IModelContentChangedEvent) {
const edits = change.changes.map(c => {
const range = Range.lift(c.range);
return new TextEditInfo(
positionToLength(range.getStartPosition()),
positionToLength(range.getEndPosition()),
lengthOfString(c.text)
);
}).reverse();
this.astWithTokens = this.parseDocumentFromTextBuffer(edits, this.astWithTokens, false);
if (this.initialAstWithoutTokens) {
this.initialAstWithoutTokens = this.parseDocumentFromTextBuffer(edits, this.initialAstWithoutTokens, false);
}
}

  • I'll set up a meeting for early next month to walk through the actual parsing code to see if we can come up with ideas to optimize

@vscodenpa vscodenpa added unreleased Patch has not yet been released in VS Code Insiders insiders-released Patch has been released in VS Code Insiders and removed unreleased Patch has not yet been released in VS Code Insiders labels Nov 15, 2022
@hediet
Copy link
Member

hediet commented Nov 28, 2022

@Tyriar can you verify this issue?

@hediet hediet added the verification-needed Verification of issue is requested label Nov 28, 2022
@Tyriar
Copy link
Member Author

Tyriar commented Nov 29, 2022

I do see it happening twice on a single keypress, but one of them is delayed until stick scroll renders which occurs after provideInlineCompletions triggers another tokenization. So the problem I was pointing out that affects input latency is fixed 👌

@Tyriar Tyriar added the verified Verification succeeded label Nov 29, 2022
@github-actions github-actions bot locked and limited conversation to collaborators Dec 30, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
editor-bracket-matching Editor brace matching feature-request Request for new features or functionality insiders-released Patch has been released in VS Code Insiders perf verification-needed Verification of issue is requested verified Verification succeeded
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants