Skip to content

Performance bottleneck with external source map sources #40054

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

Closed
JoostK opened this issue Aug 14, 2020 · 0 comments · Fixed by #40055
Closed

Performance bottleneck with external source map sources #40054

JoostK opened this issue Aug 14, 2020 · 0 comments · Fixed by #40055
Assignees
Labels
Domain: Performance Reports of unusually slow behavior Fix Available A PR has been opened for this issue Needs Investigation This issue needs a team member to investigate its status.

Comments

@JoostK
Copy link
Contributor

JoostK commented Aug 14, 2020

TypeScript Version: 3.9.7

Search Terms: external source maps, performance, setSourceMapSource

Code

I am investigating Angular compiler performance and noticed an interesting performance cliff related to TypeScript's ability to map into external sources. External source map sources are used in ngc (Angular's tsc) to map into the external .html template files, using the ts.createSourceMapSource and ts.setSourceMapRange public APIs.

During emit, emitSourcePos is used to build the source map, using a fast-path if no external source map source is used:

function emitSourcePos(source: SourceMapSource, pos: number) {
if (source !== sourceMapSource) {
const savedSourceMapSource = sourceMapSource;
setSourceMapSource(source);
emitPos(pos);
setSourceMapSource(savedSourceMapSource);
}
else {
emitPos(pos);
}
}

With an external source map, however, the slow-path that calls into setSourceMapSource is run twice, for each emitted ts.Node. This calls into ts.SourceMapGenerator.addSource which manages the source indices per source map source, based on their canonicalized relative path:

function addSource(fileName: string) {
enter();
const source = getRelativePathToDirectoryOrUrl(sourcesDirectoryPath,
fileName,
host.getCurrentDirectory(),
host.getCanonicalFileName,
/*isAbsolutePathAnUrl*/ true);
let sourceIndex = sourceToSourceIndexMap.get(source);
if (sourceIndex === undefined) {
sourceIndex = sources.length;
sources.push(source);
rawSources.push(fileName);
sourceToSourceIndexMap.set(source, sourceIndex);
}
exit();
return sourceIndex;
}

The path manipulation is somewhat expensive and it calls repeatedly into ts.CompilerHost.getCanonicalFileName, so its performance characteristics also have quite a significant impact.

export function getRelativePathToDirectoryOrUrl(directoryPathOrUrl: string, relativeOrAbsolutePath: string, currentDirectory: string, getCanonicalFileName: GetCanonicalFileName, isAbsolutePathAnUrl: boolean) {
const pathComponents = getPathComponentsRelativeTo(
resolvePath(currentDirectory, directoryPathOrUrl),
resolvePath(currentDirectory, relativeOrAbsolutePath),
equateStringsCaseSensitive,
getCanonicalFileName
);
const firstComponent = pathComponents[0];
if (isAbsolutePathAnUrl && isRootedDiskPath(firstComponent)) {
const prefix = firstComponent.charAt(0) === directorySeparator ? "file://" : "file:///";
pathComponents[0] = prefix + firstComponent;
}
return getPathFromPathComponents(pathComponents);
}

Using a program with the following characteristics (obtained with tsc --diagnostics):

Files:             1095
Lines:           200035
Nodes:           501580
Identifiers:     164978
Symbols:         202287
Types:            62907
Instantiations:   49308

we're seeing 4.8 seconds spent in emitSourcePos using ngc (of which 4.3 seconds is spent in setSourceMapSource), where the full emit phase takes 13.4 seconds; i.e. source mapping takes 36% of emit (using a profiler, so there's some overhead there). More importantly, the slow path in setSourceMapSource is responsible for 4.3/4.8 = ~90% overhead.

Using Angular CLI it's much worse with emitSourcePos taking ~16s, given that the ts.CompilerHost.getCanonicalFileName implementation is a lot slower—that is for Angular itself to improve.

Expected behavior:

The overhead in setSourceMapSource should not be 90% of total source mapping time.

Actual behavior:

Using external source map sources does have a significant performance overhead, increasing source map times ten-fold or worse.

Playground Link:
n/a

Related Issues:
n/a

@RyanCavanaugh RyanCavanaugh added the Needs Investigation This issue needs a team member to investigate its status. label Aug 17, 2020
@RyanCavanaugh RyanCavanaugh added this to the TypeScript 4.1.0 milestone Aug 17, 2020
@typescript-bot typescript-bot added the Fix Available A PR has been opened for this issue label Aug 19, 2020
@DanielRosenwasser DanielRosenwasser added the Domain: Performance Reports of unusually slow behavior label Aug 19, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Domain: Performance Reports of unusually slow behavior Fix Available A PR has been opened for this issue Needs Investigation This issue needs a team member to investigate its status.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants