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

UpdateProjectIfDirty is slow on large projects #60633

Open
dbaeumer opened this issue Nov 28, 2024 · 17 comments
Open

UpdateProjectIfDirty is slow on large projects #60633

dbaeumer opened this issue Nov 28, 2024 · 17 comments
Assignees
Labels
Needs Investigation This issue needs a team member to investigate its status.

Comments

@dbaeumer
Copy link
Member

πŸ”Ž Search Terms

UpdateProjectIfDirty language server slow

πŸ•— Version & Regression Information

  • tested the version that ships with VS Code

⏯ Playground Link

No response

πŸ’» Code

Steps to reproduce:

  • clone VS Code (https://github.com/microsoft/vscode.git)
  • npm install
  • open VS Code on the vscode repository
  • open file task.constribution.ts
  • open the TS server log
  • add a new line to _ignoreEventForUpdateRunningTasksCount

Observe: updateGraphWorker takes roughly 300ms on my machine

I took a performance trace and a lot of time is spent in GC and getNormalizedAbsolutePath. I added a simply unbound cache to the method like

const pathCache = new Map();
function getNormalizedAbsolutePath(fileName, currentDirectory) {
  let cache = pathCache.get(currentDirectory);
  if (cache === undefined) {
    cache = new Map();
    pathCache.set(currentDirectory, cache);
  }
  let normalized = cache.get(fileName);
  if (normalized === undefined) {
    normalized = getPathFromPathComponents(getNormalizedPathComponents(fileName, currentDirectory));
    cache.set(fileName, normalized);
  }
  return normalized;
}

which cuts the time for updateGraphWorker in half. However there are still some GCs going on and a lot of time is now spent in verifyCompilerOptions / getNormalizedPathComponents.

πŸ™ Actual behavior

Takes 300ms to update on key stroke

πŸ™‚ Expected behavior

Adding a new line should be even in large project only cost a couple of ms :-)

Additional information about the issue

No response

@dbaeumer
Copy link
Member Author

Doing a comparable cache in getNormalizedPathComponents brings the time down to 75ms.

vscode-profile-2024-11-28-09-36-24.cpuprofile
vscode-profile-2024-11-28-12-42-42.cpuprofile
vscode-profile-2024-11-28-15-29-10.cpuprofile

Here are also some of the CPU profiles I captured.

@RyanCavanaugh RyanCavanaugh added the Needs Investigation This issue needs a team member to investigate its status. label Dec 2, 2024
@RyanCavanaugh RyanCavanaugh added this to the TypeScript 5.8.0 milestone Dec 2, 2024
@DanielRosenwasser
Copy link
Member

DanielRosenwasser commented Dec 2, 2024

This might make things a bit easier to iterate on.

git clone https://github.com/microsoft/vscode
cd vscode

npm ci --ignore-scripts
npm install @typescript/server-replay --no-save --ignore-scripts

cat > replay-script.txt << EOF
{"seq": 1, "type": "request", "command": "updateOpen", "arguments": { "changedFiles": [],"closedFiles":[],"openFiles":[{"file":"@PROJECT_ROOT@/src/vs/workbench/contrib/tasks/browser/task.contribution.ts","projectRootPath":"@PROJECT_ROOT@"}]} }
{"seq": 2, "type": "request", "command": "updateOpen", "arguments": { "changedFiles": [{ "fileName": "@PROJECT_ROOT@/src/vs/workbench/contrib/tasks/browser/task.contribution.ts", "textChanges": [{ "newText": "\n\t\t", "start": { "line": 146, "offset": 78 }, "end": { "line": 146, "offset": 78 } }] }], "closedFiles": [], "openFiles": [] } }
{"seq": 3, "type": "request", "command": "geterr", "arguments": { "delay": 0, "files": [ { "file": "@PROJECT_ROOT@/src/vs/workbench/contrib/tasks/browser/task.contribution.ts"} ] } }
EOF

npx tsreplay ./ replay-script.txt ./node_modules/typescript/lib/tsserver.js --logDir ./

@DanielRosenwasser
Copy link
Member

@dbaeumer what platform have you experienced this on? On a Linux Codespace, I'm seeing something like 80ms. I'm guessing you might be hitting this on Windows though.

@dbaeumer
Copy link
Member Author

@DanielRosenwasser I was hitting this on WSL on my laptop. But even 80ms is a lot for a space in a file. When I looked at the code I was under the impression that most of the things that are done around path computations are 'unnecessary'.

@DanielRosenwasser
Copy link
Member

Just to keep you updated, @andrewbranch will be looking into this.

@andrewbranch
Copy link
Member

I get 95ms on WSL on a dev box. Should the difference between a dev box and your laptop be 3x+? @dbaeumer can you send a TS Server log?

@dbaeumer
Copy link
Member Author

I can produce a server log. But even 95 ms is IMO a lot considering that most of the work is around normalizing paths that are already normalized.

@dbaeumer
Copy link
Member Author

Here is a tsserver.log from my laptop running on a power adapter. I see times between 170 and 300 ms.

tsserver.zip

@DanielRosenwasser
Copy link
Member

Thanks, we'll take a look!

Just to test things out, #60754 (comment) and #60755 (comment) each have a build you can try out. Can you give us a sense of if either of these fix the issue on your side? If you can profile while running to give us a sense of where the remaining major work is, that would be helpful.

@dbaeumer
Copy link
Member Author

Times for build 60754 were between 78 - 150 ms with server plugins enabled.
Times for build 60755 were between 66 - 109 ms with server plugins enabled.

@DanielRosenwasser
Copy link
Member

@dbaeumer great! Can you let us know how the two fixes combine? I've readied a build over at #60800 (comment)

@DanielRosenwasser
Copy link
Member

It would also be helpful to see if the latest changes at #60755 (comment) still get the same wins overall. Thanks for your patience on this.

@dbaeumer
Copy link
Member Author

@DanielRosenwasser was busy the whole day with a recovery build. Will look into it first thing tomorrow.

@dbaeumer
Copy link
Member Author

Build from #60800 results in times between 65 and 110 ms

@dbaeumer
Copy link
Member Author

For 60755 times were between 172ms and 87ms

Would there be any way for me to automate this so that I can run something in a loop. I am wondering because JIT might have some impact on this as well.

@dbaeumer
Copy link
Member Author

When I debugged / traced this I was wondering if it is necessary to create a new program at all in most cases. I know reusing stuff can be tricky but a lot of changes happen inside code which don't change the structure of a program.

@jakebailey
Copy link
Member

Would there be any way for me to automate this so that I can run something in a loop. I am wondering because JIT might have some impact on this as well.

You can use https://www.npmjs.com/package/@typescript/server-harness which is what #60633 (comment) is using under the hood.

When I debugged / traced this I was wondering if it is necessary to create a new program at all in most cases. I know reusing stuff can be tricky but a lot of changes happen inside code which don't change the structure of a program.

That is what Program is doing; it's a snapshot and we carry state between them (Andrew's PR is affecting the perf of that process)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs Investigation This issue needs a team member to investigate its status.
Projects
None yet
Development

No branches or pull requests

5 participants