Skip to content

Project references decrease performance of language service by a factor of 10 #42607

Closed

Description

Bug Report

We have a large mono repo with around 10000 tsconfig.json projects in a nested directory structure configured with project references. We've been finding the performance of type checking and suggestions in both VSCode and Intellij is becoming unbearably slow, especially in projects that have a lot project ref dependencies (including transitive). For example, it's common for type errors and suggestions to take greater than 20 seconds to update and appear. I have tried doing a tsc -b on the root projects so the d.ts files are available on disk, but that doesn't seem to help.

However, I found after deleting the references field in the tsconfig files, the IDE response time reduces to 500ms, a huge improvement that makes editing a lot easier. This surprises me as I thought project references would improve performance, not decrease it. Keeping the references field but setting composite to false is still slow.

I verified the times by looking at updateGraphWorker events in the VSCode ts server logs, but the difference is also very visible without the logs.

Thanks for your help!

A sample project:

{
  "include": [
    "../../../../declarations/css_modules.d.ts",
    "./**/*.*"
  ],
  "compilerOptions": {
    "types": []
  },
  "references": [
    { "path": "../../../base" },
    { "path": "../../../base/foo" },
    { "path": "./banana" },
  ],
  "exclude": [
    "banana",
    "stories",
    "tests"
  ],
  "extends": "../../../../tsconfig.build.json"
}

The inherited tsconfig.build.json config:

{
  "compilerOptions": {
    "composite": true,
    "declaration": true,
    "declarationMap": true,
    "baseUrl": "./src",
    "experimentalDecorators": true,
    "importHelpers": true,
    "jsx": "react",
    "lib": [
      "dom",
      "dom.iterable",
      "es2018",
      "es2019.array"
    ],
    "moduleResolution": "node",
    "rootDir": ".",
    "outDir": "target",
    "strict": true,
    "skipLibCheck": true,
    "target": "ES2019",
    "sourceMap": true,
    "inlineSources": true,
    "module": "ESNext",
    "types": [],
    "paths": {
      "base": ["../node_modules/base/"],
      "mobx": ["../declarations/mobx"]
    }
  },
  // Don't include any source files in the base build.
  "include": []
}

🔎 Search Terms

language service intellisense project references performance

🕗 Version & Regression Information

Version 4.1.2

🙁 Actual behavior

Language service performance degrades when project references are defined.

🙂 Expected behavior

Language service performance does not degrade when project references are defined.

Activity

  1. ajafff commented on Feb 2, 2021

    @ajafff
    Contributor

    I have tried doing a tsc -b on the root projects so the d.ts files are available on disk, but that doesn't seem to help.

    That doesn't help because by default TypeScript will load the source files of referenced projects instead of their declaration files. That means in the worst case you have one project that loads and watches all files from your 1000 other projects.
    There's a compilerOption disableSourceOfProjectReferenceRedirect that disables that behavior in the language service and only loads the declaration files. But that means you always have to compile referenced projects to get up-to-date type information in dependent projects while editing.

  2. WearyMonkey commented on Feb 2, 2021

    @WearyMonkey
    Author

    Thanks Klaus Meinhardt (@ajafff) I've tried disableSourceOfProjectReferenceRedirect and verified via the service logs that its opening d.ts files but it didn't help performance. I also tried disableReferencedProjectLoad to no avail.

  3. WearyMonkey commented on Feb 2, 2021

    @WearyMonkey
    Author

    I've added a custom language service plugin:

    export function main() {
      return {
        create(info: ts.server.PluginCreateInfo) {
          info.languageServiceHost.getProjectReferences = () => [];
          info.project.projectService.logger.info('Installed remove_references plugin.');
          return info.languageService;
        },
      };
    }

    And that does have the same effect of removing the project refs from the tsconfig.json files, improving performance significantly.

  4. sheetalkamat commented on Feb 3, 2021

    @sheetalkamat
    Member

    We would need tsserver log to see whats going on what could be causing the delays..

  5. WearyMonkey commented on Feb 4, 2021

    @WearyMonkey
    Author

    Thanks Sheetal Nandi (@sheetalkamat) . Do you have any tools or guidance for sanitising the logs so they don't contain any private IP? It looks like they contain source file names and some source content / ASTs?

    Or is there someway we can send the logs in private?

    Thanks!

  6. WearyMonkey commented on Feb 4, 2021

    @WearyMonkey
    Author

    Sheetal Nandi (@sheetalkamat) I took some CPU profiles with and without the project references.
    With project references, about 4s is spent in canReuseProjectReferences.

    It looks like on every change the language service needs to re-parse every tsconfig.json file, which is 7000 for us. It also needs walk the directory structure of every project as well.

    New_Tab

    I tried hard coding canReuseProjectReferences to true, but the following line in tryReuseStructureFromOldProgram causes a reparse anyway.

    if (!canReuseProjectReferences()) { // causes every tsconfig.json to be relaoded.
      return StructureIsReused.Not;
    }
    if (projectReferences) {
       // causes every tsconfig.json to be relaoded.
      resolvedProjectReferences = projectReferences.map(parseProjectReferenceConfigFile);
    }
    
  7. sheetalkamat commented on Feb 4, 2021

    @sheetalkamat
    Member

    As part of #37304 investigation i am trying to cache the project rereference parsing so that might help i think.

  8. WearyMonkey commented on Feb 4, 2021

    @WearyMonkey
    Author

    Thanks Sheetal Nandi (@sheetalkamat)! I can help with testing any changes on our large code base.

  9. sheetalkamat commented on Mar 9, 2021

    @sheetalkamat
    Member

    Toby Rahilly (@WearyMonkey) can you please test this with #42929 (comment) and see if there are improvements. Thanks

  10. WearyMonkey commented on Mar 10, 2021

    @WearyMonkey
    Author

    Sheetal Nandi (@sheetalkamat)

    here are my timings:

    4.2.2 4.3 42929 4.2.2 no refs 4.3 42929 no refs
    Initial load 47341 52686 29253 35755
    Update 1 12610 1757 1532 1506
    Update 2 7417 1907 1288 1391
    Update 3 12502 1454 1269 1303

    To test, I added the line const foo: string = 1; to a root package in our project with thousands of transitive dependencies.

    Initial load is the initial updateGraphWorker time.
    Update 1 is updateGraphWorker when changing to const foo: number = 1;
    Update 2 is updateGraphWorker when changing to const foo: string = 1;
    Update 3 is updateGraphWorker when changing to const foo: number = 1;

    Interestingly I noticed updateGraphWorker is called twice per update, each taking roughly the same amount of time. So the above numbers are the sum of both updateGraphWorker tasks.

    The no refs times are with my hacky plugin which removes the project references:

    module.exports = function main() {
      return {
        create(info) {
          info.languageServiceHost.getCompilationSettings().composite = false;
          info.languageServiceHost.getProjectReferences = () => [];
          info.project.projectService.logger.info('Installed remove_references plugin.');
          return info.languageService;
        },
      };
    }
    

    So all in, a huge improvement, nice work! 👏 Super excited for 4.3 now.

  11. WearyMonkey commented on Mar 10, 2021

    @WearyMonkey
    Author

    Sorry got the data columns in wrong order in initial reply, fixed now but pointing it out in case you already looked at the wrong version.

    You can see there is a slight increase in initial load time for 4.3, which isn't as much as an issue as the update times.

  12. sheetalkamat commented on Mar 10, 2021

    @sheetalkamat
    Member

    Can you please share numbers on 4.3 with typescript@next and with #42929 and their corresponding tsserver log so i can look through if there is something we are missing or some issues with how updates are happening. (Would prefer if you can disable all plugins and extensions).
    Thanks

  13. WearyMonkey commented on Mar 10, 2021

    @WearyMonkey
    Author

    and their corresponding tsserver log

    I can't share the log sorry, it contains too much IP.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Fix AvailableA PR has been opened for this issueNeeds InvestigationThis issue needs a team member to investigate its status.

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions