From 0ff0da0f336a1d0028206096a3c932654b8e28c7 Mon Sep 17 00:00:00 2001 From: luojiyin Date: Mon, 7 Sep 2026 11:20:00 +0800 Subject: [PATCH] perf(source-map): reuse resolved segments in range queries resolveSegmentRange() already binary-searches for start and end segment indices, then calls sourceOffsetAt() which re-does the same search via findSegmentAt(). Inline the offset calculation to drop 2 redundant binary searches per range query. Remove the now-dead sourceOffsetAt() function. --- scripts/bench-source-map.mjs | 21 +++++++++++-- src/source-map/build-source-map.ts | 49 +++++++++--------------------- 2 files changed, 33 insertions(+), 37 deletions(-) diff --git a/scripts/bench-source-map.mjs b/scripts/bench-source-map.mjs index d506c93..2e1d062 100644 --- a/scripts/bench-source-map.mjs +++ b/scripts/bench-source-map.mjs @@ -114,11 +114,11 @@ for (const kib of SIZES_KIB) { [ 'query full value range', () => { - for (let r = 0; r < 1000; r++) sourceMap.getSourceRange(node, 0, len); + for (let r = 0; r < 10_000; r++) sourceMap.getSourceRange(node, 0, len); }, ], [ - 'random queries', + 'random single-unit queries', () => { let seed = 12345; const rand = () => { @@ -131,6 +131,21 @@ for (const kib of SIZES_KIB) { } }, ], + [ + 'random range queries', + () => { + let seed = 12345; + const rand = () => { + seed = (seed * 1103515245 + 12345) & 0x7FFFFFFF; + return seed / 0x7FFFFFFF; + }; + for (let r = 0; r < 10_000; r++) { + const a = Math.floor(rand() * len); + const b = Math.floor(rand() * (len - a)) + a + 1; + sourceMap.getSourceRange(node, a, Math.min(b, len)); + } + }, + ], [ 'sequential span queries', () => { @@ -144,7 +159,7 @@ for (const kib of SIZES_KIB) { const selectedPatterns = SMOKE ? [patterns[0]] : patterns; for (const [label, fn] of selectedPatterns) { - const r = time(label, fn); + const r = timeMedian(label, fn); console.log(` ${r.label.padEnd(28)} ${fmt(r.ms)}`); if (SMOKE && r.ms > SMOKE_QUERY_BUDGET_MS) { diff --git a/src/source-map/build-source-map.ts b/src/source-map/build-source-map.ts index ed844d7..038abac 100644 --- a/src/source-map/build-source-map.ts +++ b/src/source-map/build-source-map.ts @@ -424,25 +424,6 @@ function validateValueRange( }; } -function sourceOffsetAt( - segments: MarkdownSourceMapSegment[], - valueLength: number, - valueIndex: number, - pastUnit: boolean, - incompleteMessage: string, -): number { - const segment = findSegmentAt(segments, valueIndex); - if (!segment) { - if (valueIndex === valueLength && segments.length > 0) - return segments[segments.length - 1].sourceEnd; - throw new RangeError(incompleteMessage); - } - if (segment.kind !== 'literal') - return pastUnit ? segment.sourceEnd : segment.sourceStart; - const units = (pastUnit ? valueIndex + 1 : valueIndex) - segment.valueStart; - return segment.sourceStart + units; -} - function resolveEmptyRange( options: SegmentRangeOptions, ): number { @@ -473,7 +454,6 @@ function resolveEmptyRange( function resolveSegmentRange(options: SegmentRangeOptions): ParsedPosition { const { segments, - valueLength, valueStart, valueEnd, sourceGapPrefix, @@ -506,20 +486,21 @@ function resolveSegmentRange(options: SegmentRangeOptions): ParsedPosition { throw new RangeError(messages.nonContiguous); } - const startOffset = sourceOffsetAt( - segments, - valueLength, - valueStart, - false, - messages.incomplete, - ); - const endOffset = sourceOffsetAt( - segments, - valueLength, - valueEnd - 1, - true, - messages.incomplete, - ); + const startSegment = segments[startSegmentIndex]; + const endSegment = segments[endSegmentIndex]; + + let startOffset: number; + if (startSegment.kind !== 'literal') + startOffset = startSegment.sourceStart; + else + startOffset = startSegment.sourceStart + valueStart - startSegment.valueStart; + + let endOffset: number; + if (endSegment.kind !== 'literal') + endOffset = endSegment.sourceEnd; + else + endOffset = endSegment.sourceStart + (valueEnd - 1) + 1 - endSegment.valueStart; + return { start: pointAtOffset(lineStarts, source, startOffset), end: pointAtOffset(lineStarts, source, endOffset),