Skip to content

Commit a8e7a23

Browse files
authored
Merge pull request #42 from connor4312/connor4312/41
fix: pathological slow case in bySource
2 parents f27868e + 2042d5f commit a8e7a23

File tree

4 files changed

+90
-94
lines changed

4 files changed

+90
-94
lines changed

packages/trace-mapping/benchmark/index.mjs

Lines changed: 71 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { SourceMapConsumer as SourceMapConsumerWasm } from 'source-map-wasm';
2121
import { SourceMap as ChromeMap } from './chrome.mjs';
2222

2323
const dir = relative(process.cwd(), dirname(fileURLToPath(import.meta.url)));
24-
const diff = !!process.env.DIFF;
24+
const { DIFF, FILE } = process.env;
2525

2626
console.log(`node ${process.version}\n`);
2727

@@ -76,43 +76,53 @@ async function bench(file) {
7676
currentDecoded = await track('trace-mapping decoded', results, () => {
7777
const decoded = new CurrentTraceMap(decodedMapData);
7878
currentTraceSegment(decoded, 0, 0);
79+
currentGeneratedPositionFor(decoded, { source: decoded.sources[0], line: 1, column: 0 });
7980
return decoded;
8081
});
82+
const firstSource = currentDecoded.sources[0];
8183
currentEncoded = await track('trace-mapping encoded', results, () => {
8284
const encoded = new CurrentTraceMap(encodedMapData);
8385
currentTraceSegment(encoded, 0, 0);
86+
currentGeneratedPositionFor(encoded, { source: firstSource, line: 1, column: 0 });
8487
return encoded;
8588
});
86-
if (diff) {
89+
if (DIFF) {
8790
latestDecoded = await track('trace-mapping latest decoded', results, () => {
8891
const decoded = new LatestTraceMap(decodedMapData);
8992
latestTraceSegment(decoded, 0, 0);
93+
latestGeneratedPositionFor(decoded, { source: firstSource, line: 1, column: 0 });
9094
return decoded;
9195
});
9296
latestEncoded = await track('trace-mapping latest encoded', results, () => {
9397
const encoded = new LatestTraceMap(encodedMapData);
9498
latestTraceSegment(encoded, 0, 0);
99+
latestGeneratedPositionFor(encoded, { source: firstSource, line: 1, column: 0 });
95100
return encoded;
96101
});
97102
} else {
98103
smcjs = await track('source-map-js', results, () => {
99104
const smcjs = new SourceMapConsumerJs(encodedMapData);
100105
smcjs.originalPositionFor({ line: 1, column: 0 });
106+
smcjs.generatedPositionFor({ source: firstSource, line: 1, column: 0 });
101107
return smcjs;
102108
});
103109
smc061 = await track('source-map-0.6.1', results, () => {
104110
const smc061 = new SourceMapConsumer061(encodedMapData);
105111
smc061.originalPositionFor({ line: 1, column: 0 });
112+
smc061.generatedPositionFor({ source: firstSource, line: 1, column: 0 });
106113
return smc061;
107114
});
108115
smcWasm = await track('source-map-0.8.0', results, async () => {
109116
const smcWasm = await new SourceMapConsumerWasm(encodedMapData);
110117
smcWasm.originalPositionFor({ line: 1, column: 0 });
118+
smcWasm.generatedPositionFor({ source: firstSource, line: 1, column: 0 });
111119
return smcWasm;
112120
});
113121
chromeMap = await track('Chrome dev tools', results, async () => {
114122
const map = new ChromeMap('url', encodedMapData);
115123
map.findEntry(0, 0);
124+
const firstSource = map.sources()[0];
125+
map.findEntryReversed(firstSource, 6);
116126
return map;
117127
});
118128
}
@@ -138,7 +148,7 @@ async function bench(file) {
138148
.add('trace-mapping: encoded Object input', () => {
139149
currentTraceSegment(new CurrentTraceMap(encodedMapData), 0, 0);
140150
});
141-
if (diff) {
151+
if (DIFF) {
142152
benchmark = benchmark
143153
.add('trace-mapping latest: decoded JSON input', () => {
144154
latestTraceSegment(new LatestTraceMap(decodedMapDataJson), 0, 0);
@@ -203,7 +213,7 @@ async function bench(file) {
203213
currentTraceSegment(currentEncoded, i, column);
204214
}
205215
});
206-
if (diff) {
216+
if (DIFF) {
207217
benchmark = benchmark
208218
.add('trace-mapping latest: decoded originalPositionFor', () => {
209219
const i = Math.floor(Math.random() * lines.length);
@@ -309,7 +319,7 @@ async function bench(file) {
309319
currentTraceSegment(currentEncoded, i, column);
310320
}
311321
});
312-
if (diff) {
322+
if (DIFF) {
313323
benchmark = benchmark
314324
.add('trace-mapping latest: decoded originalPositionFor', () => {
315325
const i = Math.floor(Math.random() * lines.length);
@@ -388,15 +398,13 @@ async function bench(file) {
388398
console.log('');
389399

390400
console.log('Generated Positions init:');
391-
const firstSource = currentDecoded.sources[0];
392401
benchmark = new Benchmark.Suite()
393402
.add('trace-mapping: decoded generatedPositionFor', () => {
394403
const decoded = new CurrentTraceMap(decodedMapData);
395404
currentGeneratedPositionFor(decoded, {
396405
source: firstSource,
397406
line: 6,
398407
column: 0,
399-
bias: 1,
400408
});
401409
})
402410
.add('trace-mapping: encoded generatedPositionFor', () => {
@@ -405,10 +413,9 @@ async function bench(file) {
405413
source: firstSource,
406414
line: 6,
407415
column: 0,
408-
bias: 1,
409416
});
410417
});
411-
if (diff) {
418+
if (DIFF) {
412419
benchmark = benchmark
413420
.add('trace-mapping latest: decoded generatedPositionFor', () => {
414421
const decoded = new LatestTraceMap(decodedMapData);
@@ -454,6 +461,7 @@ async function bench(file) {
454461
})
455462
.add('Chrome dev tools: encoded generatedPositionFor', () => {
456463
const chromeMap = new ChromeMap('url', encodedMapData);
464+
const firstSource = chromeMap.sources()[0];
457465
chromeMap.findEntryReversed(firstSource, 6);
458466
});
459467
}
@@ -473,62 +481,76 @@ async function bench(file) {
473481
console.log('Generated Positions speed:');
474482
benchmark = new Benchmark.Suite()
475483
.add('trace-mapping: decoded generatedPositionFor', () => {
476-
currentGeneratedPositionFor(currentDecoded, {
477-
source: firstSource,
478-
line: 6,
479-
column: 0,
480-
bias: 1,
481-
});
484+
for (const source of currentDecoded.sources) {
485+
currentGeneratedPositionFor(currentDecoded, {
486+
source,
487+
line: 6,
488+
column: 0,
489+
});
490+
}
482491
})
483492
.add('trace-mapping: encoded generatedPositionFor', () => {
484-
currentGeneratedPositionFor(currentEncoded, {
485-
source: firstSource,
486-
line: 6,
487-
column: 0,
488-
bias: 1,
489-
});
490-
});
491-
if (diff) {
492-
benchmark = benchmark
493-
.add('trace-mapping latest: decoded generatedPositionFor', () => {
494-
latestGeneratedPositionFor(latestDecoded, {
495-
source: firstSource,
493+
for (const source of currentEncoded.sources) {
494+
currentGeneratedPositionFor(currentEncoded, {
495+
source,
496496
line: 6,
497497
column: 0,
498498
});
499+
}
500+
});
501+
if (DIFF) {
502+
benchmark = benchmark
503+
.add('trace-mapping latest: decoded generatedPositionFor', () => {
504+
for (const source of latestDecoded.sources) {
505+
latestGeneratedPositionFor(latestDecoded, {
506+
source,
507+
line: 6,
508+
column: 0,
509+
});
510+
}
499511
})
500512
.add('trace-mapping latest: encoded generatedPositionFor', () => {
501-
latestGeneratedPositionFor(latestEncoded, {
502-
source: firstSource,
503-
line: 6,
504-
column: 0,
505-
});
513+
for (const source of latestEncoded.sources) {
514+
latestGeneratedPositionFor(latestEncoded, {
515+
source,
516+
line: 6,
517+
column: 0,
518+
});
519+
}
506520
});
507521
} else {
508522
benchmark = benchmark
509523
.add('source-map-js: encoded generatedPositionFor', () => {
510-
smcjs.generatedPositionFor({
511-
source: firstSource,
512-
line: 6,
513-
column: 0,
514-
});
524+
for (const source of smcjs.sources) {
525+
smcjs.generatedPositionFor({
526+
source,
527+
line: 6,
528+
column: 0,
529+
});
530+
}
515531
})
516532
.add('source-map-0.6.1: encoded generatedPositionFor', () => {
517-
smc061.generatedPositionFor({
518-
source: firstSource,
519-
line: 6,
520-
column: 0,
521-
});
533+
for (const source of smc061.sources) {
534+
smc061.generatedPositionFor({
535+
source,
536+
line: 6,
537+
column: 0,
538+
});
539+
}
522540
})
523541
.add('source-map-0.8.0: encoded generatedPositionFor', () => {
524-
smcWasm.generatedPositionFor({
525-
source: firstSource,
526-
line: 6,
527-
column: 0,
528-
});
542+
for (const source of smcWasm.sources) {
543+
smcWasm.generatedPositionFor({
544+
source,
545+
line: 6,
546+
column: 0,
547+
});
548+
}
529549
})
530550
.add('Chrome dev tools: encoded generatedPositionFor', () => {
531-
chromeMap.findEntryReversed(firstSource, 6);
551+
for (const source of chromeMap.sources()) {
552+
chromeMap.findEntryReversed(source, 6);
553+
}
532554
});
533555
}
534556
// add listeners
@@ -549,7 +571,7 @@ async function run(files) {
549571
let first = true;
550572
for (const file of files) {
551573
if (!file.endsWith('.map')) continue;
552-
if (file !== 'issue-41.js.map') continue;
574+
if (FILE && file !== FILE) continue;
553575

554576
if (!first) console.log('\n\n***\n\n');
555577
first = false;
Lines changed: 14 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,17 @@
11
import { COLUMN, SOURCES_INDEX, SOURCE_LINE, SOURCE_COLUMN } from './sourcemap-segment';
2-
import { memoizedBinarySearch, upperBound } from './binary-search';
2+
import { sortComparator } from './sort';
33

44
import type { ReverseSegment, SourceMapSegment } from './sourcemap-segment';
5-
import type { MemoState } from './binary-search';
65

7-
export type Source = {
8-
__proto__: null;
9-
[line: number]: Exclude<ReverseSegment, [number]>[];
10-
};
6+
export type Source = ReverseSegment[][];
117

128
// Rebuilds the original source files, with mappings that are ordered by source line/column instead
139
// of generated line/column.
1410
export default function buildBySources(
1511
decoded: readonly SourceMapSegment[][],
16-
memos: MemoState[],
12+
memos: unknown[],
1713
): Source[] {
18-
const sources: Source[] = memos.map(buildNullArray);
14+
const sources: Source[] = memos.map(() => []);
1915

2016
for (let i = 0; i < decoded.length; i++) {
2117
const line = decoded[i];
@@ -26,40 +22,20 @@ export default function buildBySources(
2622
const sourceIndex = seg[SOURCES_INDEX];
2723
const sourceLine = seg[SOURCE_LINE];
2824
const sourceColumn = seg[SOURCE_COLUMN];
29-
const originalSource = sources[sourceIndex];
30-
const originalLine = (originalSource[sourceLine] ||= []);
31-
const memo = memos[sourceIndex];
3225

33-
// The binary search either found a match, or it found the left-index just before where the
34-
// segment should go. Either way, we want to insert after that. And there may be multiple
35-
// generated segments associated with an original location, so there may need to move several
36-
// indexes before we find where we need to insert.
37-
let index = upperBound(
38-
originalLine,
39-
sourceColumn,
40-
memoizedBinarySearch(originalLine, sourceColumn, memo, sourceLine),
41-
);
42-
43-
memo.lastIndex = ++index;
44-
insert(originalLine, index, [sourceColumn, i, seg[COLUMN]]);
26+
const source = sources[sourceIndex];
27+
const segs = (source[sourceLine] ||= []);
28+
segs.push([sourceColumn, i, seg[COLUMN]]);
4529
}
4630
}
4731

48-
return sources;
49-
}
50-
51-
function insert<T>(array: T[], index: number, value: T) {
52-
for (let i = array.length; i > index; i--) {
53-
array[i] = array[i - 1];
32+
for (let i = 0; i < sources.length; i++) {
33+
const source = sources[i];
34+
for (let j = 0; j < source.length; j++) {
35+
const line = source[j];
36+
if (line) line.sort(sortComparator);
37+
}
5438
}
55-
array[index] = value;
56-
}
5739

58-
// Null arrays allow us to use ordered index keys without actually allocating contiguous memory like
59-
// a real array. We use a null-prototype object to avoid prototype pollution and deoptimizations.
60-
// Numeric properties on objects are magically sorted in ascending order by the engine regardless of
61-
// the insertion order. So, by setting any numeric keys, even out of order, we'll get ascending
62-
// order when iterating with for-in.
63-
function buildNullArray<T extends { __proto__: null }>(): T {
64-
return { __proto__: null } as T;
40+
return sources;
6541
}

packages/trace-mapping/src/sort.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { COLUMN } from './sourcemap-segment';
22

3-
import type { SourceMapSegment } from './sourcemap-segment';
3+
import type { ReverseSegment, SourceMapSegment } from './sourcemap-segment';
44

55
export default function maybeSort(
66
mappings: SourceMapSegment[][],
@@ -40,6 +40,6 @@ function sortSegments(line: SourceMapSegment[], owned: boolean): SourceMapSegmen
4040
return line.sort(sortComparator);
4141
}
4242

43-
function sortComparator(a: SourceMapSegment, b: SourceMapSegment): number {
43+
export function sortComparator<T extends SourceMapSegment | ReverseSegment>(a: T, b: T): number {
4444
return a[COLUMN] - b[COLUMN];
4545
}

packages/trace-mapping/src/trace-mapping.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -484,15 +484,13 @@ function generatedPosition(
484484
if (sourceIndex === -1) sourceIndex = resolvedSources.indexOf(source);
485485
if (sourceIndex === -1) return all ? [] : GMapping(null, null);
486486

487-
const generated = (cast(map)._bySources ||= buildBySources(
488-
decodedMappings(map),
489-
(cast(map)._bySourceMemos = sources.map(memoizedState)),
490-
));
487+
const bySourceMemos = (cast(map)._bySourceMemos ||= sources.map(memoizedState));
488+
const generated = (cast(map)._bySources ||= buildBySources(decodedMappings(map), bySourceMemos));
491489

492490
const segments = generated[sourceIndex][line];
493491
if (segments == null) return all ? [] : GMapping(null, null);
494492

495-
const memo = cast(map)._bySourceMemos![sourceIndex];
493+
const memo = bySourceMemos[sourceIndex];
496494

497495
if (all) return sliceGeneratedPositions(segments, memo, line, column, bias);
498496

0 commit comments

Comments
 (0)