TypeScript icon indicating copy to clipboard operation
TypeScript copied to clipboard

Running with custom module resolver is 3x slower, as resolution is invalidated by TS compiler every time

Open rdsedmundo opened this issue 2 years ago • 5 comments

Suggestion

This is roughly a copy of https://github.com/typescript-eslint/typescript-eslint/issues/3798, which I transferred here as suggested by the maintainers of the @typescript-eslint project as there was little they could do on their side to improve this.

🔍 Search Terms

custom module resolution module resolution invalidation user provided resolution

✅ Viability Checklist

My suggestion meets these guidelines:

  • [x] This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • [x] This wouldn't change the runtime behavior of existing JavaScript code
  • [x] This could be implemented without emitting different JS based on the types of the expressions
  • [x] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
  • [x] This feature would agree with the rest of TypeScript's Design Goals.

⭐ Suggestion

Any project that provides a custom module resolver suffers from this, I tried in different projects and got the same results.

The actual implementation of the resolveModuleNames can be as simple as (moduleNames) => moduleNames.map(n => undefined) and it is still way slower even with caching.

I have a custom module resolver with cache, and running @typescript-eslint with it presents a 3x slowdown compared to using TS's native resolution. I've debugged it a bit (with my limited knowledge about the TS compiler) and found that the root cause may be that the TS compiler is invalidating all the resolutions whenever a new file is checked because the Program is re-synchronized and re-created no matter what, as a custom module resolution was provided (comment says: "All resolutions are invalid if user provided resolutions").

Even though my custom resolveModuleName has cache and returns the cached paths immediately after the first seconds of running, the overhead of invalidating all the resolutions and re-creating the Program several times is still way too great and causes this significant slowdown.

Example run:

Original TS synchronizeProgram called 1047 times createNewProgram called 1 times resolveModuleNames called 1744 times

Custom module resolution synchronizeProgram called 1047 times createNewProgram called 1047 times resolveModuleNames called 1,766,289 times

Custom module resolution with TS compiler patched, forcing the variable userProvidedResolution=false synchronizeProgram called 1047 times createNewProgram called 1 times resolveModuleNames called 1687 times

📃 Motivating Example

This would improve the speeds of custom module resolvers.

💻 Use Cases

This would improve the speeds of custom module resolvers.

rdsedmundo avatar Feb 28 '22 11:02 rdsedmundo

@sheetalkamat / @rbuckton thoughts on what we might do here?

RyanCavanaugh avatar Feb 28 '22 18:02 RyanCavanaugh

synchronizeProgram called 1047 times createNewProgram called 1047 times resolveModuleNames called 1,766,289 times

I guess that makes sense because we’re talking about 1,744 resolutions times 1,000+ programs versus just one program, but… woah. That’s insane.

fatcerberus avatar Feb 28 '22 18:02 fatcerberus

@sheetalkamat @rbuckton any thoughts on what we could do here? I'd appreciate any insights, depending on the difficulty I could try working on a PR for it too.

rdsedmundo avatar May 12 '22 12:05 rdsedmundo

When we have our own resolutions, we know which files will be affected because we are watching required paths etc so the logic to invalidate files is in built. But thats no true with custom resolutions. We do this tracking by setting hasInvalidatedResolution function for the compiler host. It is internal for now but we could potentially think about making it public if that seems to work. Can you give that a try and see if that works for you. (Because you are doing custom resolution you would need to determine which files have invalid resolutions and they need to re-resolved) https://github.com/microsoft/TypeScript/blob/main/src/compiler/types.ts#L6792

sheetalkamat avatar May 12 '22 17:05 sheetalkamat

Sorry for the delay on this, I can confirm that using a custom hasInvalidatedResolution works fine. Since we aren't doing anything fancy but resolving absolute paths, I simply took the userProvidedResolution flag and relied on the native implementation that TS already has of checking if configs has changed and etc, and it still worked perfectly.

rdsedmundo avatar Aug 09 '22 13:08 rdsedmundo

Aside: hard-linking to that line:

https://github.com/microsoft/TypeScript/blob/1f0f7c824629e399c9dcb8e488ce9b8434c7078d/src/compiler/types.ts#L7121

DanielRosenwasser avatar Aug 19 '22 00:08 DanielRosenwasser

Will soon look into how we can make this work with user provided API.

sheetalkamat avatar Sep 02 '22 21:09 sheetalkamat