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

Improved performance of addChange() #4380

Closed
wants to merge 3 commits into from
Closed

Improved performance of addChange() #4380

wants to merge 3 commits into from

Conversation

cvaldev
Copy link
Contributor

@cvaldev cvaldev commented Dec 14, 2019

Currently, HistoryTracker.addChange() has a major impact on performance, specially when working on large files. The issue stems largely due to processing the entire document on every call, and that the function gets called on every key press. My proposed changes are as follows:

Check if offset at EOF is equals to oldText.length

Currently the first thing addChange() does is grab the text of the entire document, which is a pretty expensive operation. We then check the newText against the oldText to see if there are any changes that need to be processed. There really is no need to get the entire document to know this.

The zero based offset at the document's end represents the location of the character that would follow the last character, thus it is equal to the length of the document's text. We can easily get this offset by using vscode.TextDocument.offsetAt() and passing it Position.getDocumentEnd(). If there are no changes this offset will be equals to historyTracker.oldText.length.

This would increase the performance when navigating through large files.

Improvements while on insert mode

The major issue is that addChange() gets called on every key press, thus the entire document is getting processed every few milliseconds if we type too fast. Which is why we experience more lag the faster we make changes on large files. Currently the first change after going into insert mode triggers _addnewHistoryStep(), consequent changes are added to the historyStep and then when we get out of insert mode we finalise that historyStep.

We shouldn't really need to log each substep while we are on Insert Mode since we only go back one historyStep at a time. We can grab a snapshot of when we first start making changes and one at the end. I had initially thought we could just grab the last change, but we do need the first step to be logged so that we don't brake historyTracker.goBackHistoryStepsOnLine().

We can add a condition that checks if the currentMode === Mode.Insert to get out of addChange() and prevent processing the document while changes are still in progress. The condition shouldn't trigger if we just created a new history step, so that we may log the first step. When we get out of Insert Mode, the call from to addChange() within ModeHandler.runAction() will process the final step.

This change, while it has a major positive impact on performance, does change the workflow of the code a little bit, since calls to addChange() while we are on insert mode will not do anything. With the exception of the first change that creates the historyStep, the call made to addChange() from within ModeHandler.handleKeyEventHelper() will be go largely ignored. I don't believe this has any negative side effects, but is something we might want to be aware of.

Which issue(s) this PR fixes

Possibly fixes #3920, #2021, #2216

@TravisBuddy
Copy link

Travis tests have failed

Hey @CVVPK,
Please read the following log in order to understand the failure reason.
It'll be awesome if you fix what's wrong and commit the changes.

Node.js: 8

View build log

if [[ $(git diff-index HEAD -- *.js *.ts *.md) ]]; then git diff; echo "Prettier Failed. Run `gulp forceprettier` and commit changes to resolve."; exit 1; fi
The command "if [[ $(git diff-index HEAD -- *.js *.ts *.md) ]]; then git diff; echo "Prettier Failed. Run `gulp forceprettier` and commit changes to resolve."; exit 1; fi" exited with 0.
$ npm run build

> [email protected] build /home/travis/build/VSCodeVim/Vim
> gulp build

[03:38:08] Using gulpfile ~/build/VSCodeVim/Vim/gulpfile.js
[03:38:08] Starting 'build'...
[03:38:08] Starting 'prettier'...
[03:38:11] Finished 'prettier' after 2.55 s
[03:38:11] Starting 'tsc'...
[03:38:11] Starting 'tslint'...

ERROR: /home/travis/build/VSCodeVim/Vim/src/history/historyTracker.ts:440:1 - Exceeds maximum line length of 140

[03:38:19] 'tslint' errored after 8.48 s
[03:38:19] Error in plugin "gulp-tslint"
Message:
    Failed to lint: 1 errors.
Details:
    domain: [object Object]
    domainThrown: true

[03:38:19] 'build' errored after 11 s
[03:38:19] The following tasks did not complete: tsc
[03:38:19] Did you forget to signal async completion?
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected] build: `gulp build`
npm ERR! Exit status 1
npm ERR! 
npm ERR! Failed at the [email protected] build script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /home/travis/.npm/_logs/2019-12-14T03_38_19_772Z-debug.log
The command "npm run build" exited with 1.
$ npm test

> [email protected] test /home/travis/build/VSCodeVim/Vim
> node ./node_modules/vscode/bin/test

### VS Code Extension Test Run ###

Current working directory: /home/travis/build/VSCodeVim/Vim
Downloading VS Code 1.41.0 into .vscode-test/vscode-1.41.0.
Downloading VS Code from: https://update.code.visualstudio.com/1.41.0/linux-x64/stable
Downloaded VS Code 1.41.0
Running extension tests: /home/travis/build/VSCodeVim/Vim/.vscode-test/vscode-1.41.0/VSCode-linux-x64/code /home/travis/build/VSCodeVim/Vim/test --extensionDevelopmentPath=/home/travis/build/VSCodeVim/Vim --extensionTestsPath=/home/travis/build/VSCodeVim/Vim/test --locale=en
[main 2019-12-14T03:38:24.685Z] update#setState idle

bash: cannot set terminal process group (-1): Inappropriate ioctl for device

bash: no job control in this shell

nvm is not compatible with the "npm_config_prefix" environment variable: currently set to "/home/travis/.nvm/versions/node/v8.16.2"

Run `unset npm_config_prefix` to unset it.

Error: Error: Cannot find module '/home/travis/build/VSCodeVim/Vim/test'
Require stack:
- /home/travis/build/VSCodeVim/Vim/.vscode-test/vscode-1.41.0/VSCode-linux-x64/resources/app/out/vs/loader.js
- /home/travis/build/VSCodeVim/Vim/.vscode-test/vscode-1.41.0/VSCode-linux-x64/resources/app/out/bootstrap-amd.js
- /home/travis/build/VSCodeVim/Vim/.vscode-test/vscode-1.41.0/VSCode-linux-x64/resources/app/out/bootstrap-fork.js
	at d._doHandleExtensionTests (/home/travis/build/VSCodeVim/Vim/.vscode-test/vscode-1.41.0/VSCode-linux-x64/resources/app/out/vs/workbench/services/extensions/node/extensionHostProcess.js:699:65)

Error: Cannot find module '/home/travis/build/VSCodeVim/Vim/test'
Require stack:
- /home/travis/build/VSCodeVim/Vim/.vscode-test/vscode-1.41.0/VSCode-linux-x64/resources/app/out/vs/loader.js
- /home/travis/build/VSCodeVim/Vim/.vscode-test/vscode-1.41.0/VSCode-linux-x64/resources/app/out/bootstrap-amd.js
- /home/travis/build/VSCodeVim/Vim/.vscode-test/vscode-1.41.0/VSCode-linux-x64/resources/app/out/bootstrap-fork.js: Error: Error: Cannot find module '/home/travis/build/VSCodeVim/Vim/test'
Require stack:
- /home/travis/build/VSCodeVim/Vim/.vscode-test/vscode-1.41.0/VSCode-linux-x64/resources/app/out/vs/loader.js
- /home/travis/build/VSCodeVim/Vim/.vscode-test/vscode-1.41.0/VSCode-linux-x64/resources/app/out/bootstrap-amd.js
- /home/travis/build/VSCodeVim/Vim/.vscode-test/vscode-1.41.0/VSCode-linux-x64/resources/app/out/bootstrap-fork.js
	at d._doHandleExtensionTests (/home/travis/build/VSCodeVim/Vim/.vscode-test/vscode-1.41.0/VSCode-linux-x64/resources/app/out/vs/workbench/services/extensions/node/extensionHostProcess.js:699:65)

Tests exited with code: 1
npm ERR! Test failed.  See above for more details.
The command "npm test" exited with 1.
store build cache
changes detected (content changed, file is created, or file is deleted):\n/home/travis/.npm/_logs/2019-12-14T03_38_19_772Z-debug.log\n
changes detected, packing new archive
uploading PR.4380/cache--linux-xenial-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855--node-8.tgz
cache uploaded


Done. Your build exited with 1.
npm run build
> [email protected] build /home/travis/build/VSCodeVim/Vim
> gulp build

[03:38:08] Using gulpfile ~/build/VSCodeVim/Vim/gulpfile.js
[03:38:08] Starting 'build'...
[03:38:08] Starting 'prettier'...
[03:38:11] Finished 'prettier' after 2.55 s
[03:38:11] Starting 'tsc'...
[03:38:11] Starting 'tslint'...

ERROR: /home/travis/build/VSCodeVim/Vim/src/history/historyTracker.ts:440:1 - Exceeds maximum line length of 140

[03:38:19] 'tslint' errored after 8.48 s
[03:38:19] Error in plugin "gulp-tslint"
Message:
    Failed to lint: 1 errors.
Details:
    domain: [object Object]
    domainThrown: true

[03:38:19] 'build' errored after 11 s
[03:38:19] The following tasks did not complete: tsc
[03:38:19] Did you forget to signal async completion?
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected] build: `gulp build`
npm ERR! Exit status 1
npm ERR! 
npm ERR! Failed at the [email protected] build script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /home/travis/.npm/_logs/2019-12-14T03_38_19_772Z-debug.log
npm test
> [email protected] test /home/travis/build/VSCodeVim/Vim
> node ./node_modules/vscode/bin/test

### VS Code Extension Test Run ###

Current working directory: /home/travis/build/VSCodeVim/Vim
Downloading VS Code 1.41.0 into .vscode-test/vscode-1.41.0.
Downloading VS Code from: https://update.code.visualstudio.com/1.41.0/linux-x64/stable
Downloaded VS Code 1.41.0
Running extension tests: /home/travis/build/VSCodeVim/Vim/.vscode-test/vscode-1.41.0/VSCode-linux-x64/code /home/travis/build/VSCodeVim/Vim/test --extensionDevelopmentPath=/home/travis/build/VSCodeVim/Vim --extensionTestsPath=/home/travis/build/VSCodeVim/Vim/test --locale=en
[main 2019-12-14T03:38:24.685Z] update#setState idle

bash: cannot set terminal process group (-1): Inappropriate ioctl for device

bash: no job control in this shell

nvm is not compatible with the "npm_config_prefix" environment variable: currently set to "/home/travis/.nvm/versions/node/v8.16.2"

Run `unset npm_config_prefix` to unset it.

Error: Error: Cannot find module '/home/travis/build/VSCodeVim/Vim/test'
Require stack:
- /home/travis/build/VSCodeVim/Vim/.vscode-test/vscode-1.41.0/VSCode-linux-x64/resources/app/out/vs/loader.js
- /home/travis/build/VSCodeVim/Vim/.vscode-test/vscode-1.41.0/VSCode-linux-x64/resources/app/out/bootstrap-amd.js
- /home/travis/build/VSCodeVim/Vim/.vscode-test/vscode-1.41.0/VSCode-linux-x64/resources/app/out/bootstrap-fork.js
	at d._doHandleExtensionTests (/home/travis/build/VSCodeVim/Vim/.vscode-test/vscode-1.41.0/VSCode-linux-x64/resources/app/out/vs/workbench/services/extensions/node/extensionHostProcess.js:699:65)

Error: Cannot find module '/home/travis/build/VSCodeVim/Vim/test'
Require stack:
- /home/travis/build/VSCodeVim/Vim/.vscode-test/vscode-1.41.0/VSCode-linux-x64/resources/app/out/vs/loader.js
- /home/travis/build/VSCodeVim/Vim/.vscode-test/vscode-1.41.0/VSCode-linux-x64/resources/app/out/bootstrap-amd.js
- /home/travis/build/VSCodeVim/Vim/.vscode-test/vscode-1.41.0/VSCode-linux-x64/resources/app/out/bootstrap-fork.js: Error: Error: Cannot find module '/home/travis/build/VSCodeVim/Vim/test'
Require stack:
- /home/travis/build/VSCodeVim/Vim/.vscode-test/vscode-1.41.0/VSCode-linux-x64/resources/app/out/vs/loader.js
- /home/travis/build/VSCodeVim/Vim/.vscode-test/vscode-1.41.0/VSCode-linux-x64/resources/app/out/bootstrap-amd.js
- /home/travis/build/VSCodeVim/Vim/.vscode-test/vscode-1.41.0/VSCode-linux-x64/resources/app/out/bootstrap-fork.js
	at d._doHandleExtensionTests (/home/travis/build/VSCodeVim/Vim/.vscode-test/vscode-1.41.0/VSCode-linux-x64/resources/app/out/vs/workbench/services/extensions/node/extensionHostProcess.js:699:65)

Tests exited with code: 1
npm ERR! Test failed.  See above for more details.
TravisBuddy Request Identifier: 390ad000-1e23-11ea-a424-4f8e788bb22e


if (newText === this.oldText) {
// Check if the document's text changed.
if (newTextLength === this.oldText.length) {
Copy link
Member

@J-Fields J-Fields Dec 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we can just enumerate the special cases and deal with them separately, but doesn't this break with :s/abc/def, for instance?

Copy link
Contributor Author

@cvaldev cvaldev Dec 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe you're referring to the case of doing iabc<Esc>:s/abc/def\nu which results in | when it should be abc. It does break that, sorry. I hadn't considered that case and it seems we aren't currently testing for it. Can I add a test for it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah a test for this would be great. If we go in this direction of optimization, I'd like to make sure we cover all the edge cases

@cvaldev
Copy link
Contributor Author

cvaldev commented Dec 15, 2019

I'm closing this because I messed up my master....

@cvaldev cvaldev closed this Dec 15, 2019
J-Fields pushed a commit that referenced this pull request Jan 9, 2020
For reference, see #4380 and #3920 .

As mentioned in #4380 HistoryTracker.addChange() performance gets worse the larger the file and the faster we make individual changes, because on each change we would get the entire document's text. We don't use the individual changes, so we really only care about the initial step and the final one.

A new historyStep is created when we first enter a new mode and finalized after we exit it. We can keep track of the currentMode internally, and when it is different to vimState.currentMode we know we have switched. This way we prevent making unnecessary calls to TextDocument.getText() while changes are still in progress.

This commit also activates the optimization from #4451 when `experimentalOptimizations` is enabled.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Investigate optimizing HistoryTracker.addChange()
3 participants