-
Notifications
You must be signed in to change notification settings - Fork 681
refactor(metro-file-map): Pre-resolve symlink targets and store normal posix paths #1687
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
base: main
Are you sure you want to change the base?
Changes from 4 commits
a848cd5
d3fd493
e8a016c
e14f40a
0a64351
32f7bd4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,7 @@ import type { | |
| } from '../flow-types'; | ||
|
|
||
| import H from '../constants'; | ||
| import normalizePathSeparatorsToSystem from './normalizePathSeparatorsToSystem'; | ||
| import {RootPathUtils} from './RootPathUtils'; | ||
| import invariant from 'invariant'; | ||
| import path from 'path'; | ||
|
|
@@ -124,8 +125,6 @@ type MetadataIteratorOptions = Readonly<{ | |
| * a trailing slash | ||
| */ | ||
| export default class TreeFS implements MutableFileSystem { | ||
| +#cachedNormalSymlinkTargets: WeakMap<FileNode, NormalizedSymlinkTarget> = | ||
| new WeakMap(); | ||
| +#pathUtils: RootPathUtils; | ||
| +#processFile: ProcessFileFunction; | ||
| +#rootDir: Path; | ||
|
|
@@ -1214,33 +1213,17 @@ export default class TreeFS implements MutableFileSystem { | |
| symlinkNode: FileMetadata, | ||
| canonicalPathOfSymlink: Path, | ||
| ): NormalizedSymlinkTarget { | ||
| const cachedResult = this.#cachedNormalSymlinkTargets.get(symlinkNode); | ||
| if (cachedResult != null) { | ||
| return cachedResult; | ||
| } | ||
|
|
||
| const literalSymlinkTarget = symlinkNode[H.SYMLINK]; | ||
| let symlinkTarget = symlinkNode[H.SYMLINK]; | ||
| invariant( | ||
| typeof literalSymlinkTarget === 'string', | ||
| typeof symlinkTarget === 'string', | ||
| 'Expected symlink target to be populated.', | ||
| ); | ||
| const absoluteSymlinkTarget = path.resolve( | ||
| this.#rootDir, | ||
| canonicalPathOfSymlink, | ||
| '..', // Symlink target is relative to its containing directory. | ||
| literalSymlinkTarget, // May be absolute, in which case the above are ignored | ||
| ); | ||
| const normalSymlinkTarget = path.relative( | ||
| this.#rootDir, | ||
| absoluteSymlinkTarget, | ||
| ); | ||
| symlinkTarget = normalizePathSeparatorsToSystem(symlinkTarget); | ||
| const result = { | ||
| ancestorOfRootIdx: | ||
| this.#pathUtils.getAncestorOfRootIdx(normalSymlinkTarget), | ||
| normalPath: normalSymlinkTarget, | ||
| startOfBasenameIdx: normalSymlinkTarget.lastIndexOf(path.sep) + 1, | ||
| ancestorOfRootIdx: this.#pathUtils.getAncestorOfRootIdx(symlinkTarget), | ||
| normalPath: symlinkTarget, | ||
| startOfBasenameIdx: symlinkTarget.lastIndexOf(path.sep) + 1, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we're not caching, we may be better off computing some of this lazily (and eg, just return the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removed the intermediate data structure in 0a64351 |
||
| }; | ||
| this.#cachedNormalSymlinkTargets.set(symlinkNode, result); | ||
| return result; | ||
| } | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On the face of it, this looks like it could be a perf regression, because we're introducing a new allocation and some string manipulation every time the same symlink is traversed, vs once per symlink.
I'd be interested to see a benchmark on a symlink-heavy project. Weakmap lookups should generally be very fast.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this was admittedly tied to the on-demand file system change, which necessitated changes here anyway. It then made sense to pre-normalise values which ended up being a net neutral change (performance-wise) across all combined changes (!)
I can give benchmarking this in isolation a go next week 👍
Together with the lazy symlink resolution I would expect this to be mainly a neutral change since the old code paths assumed a low amount of symlinks. But I don't have an intuition of what this would look like in a project with many symlinks where the cost isn't negligible. My intuition overall with this change was that the cost of
WeakMaplookups can add up faster than computing the path once, provided symlink resolution is lazier than it is nowUh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to show that it's mostly neutral: kitten@eee7a82
Intuitively, the same amount of work happens (at least for macOS), since the path is prenormalised and stored in the file tree data structure, which means that the work (which was negligible to begin with) is now happening earlier and makes the
WeakMapobsolete.I didn't test this on Windows, where presumably there's a small additional cost due to the path normalisation (win -> posix -> win), which keeps the file map data structure portable. We could simply skip this and keep this denormalised, but the parity is nice imo