From 39e10507262cf695489b8239db625bd08cbc367b Mon Sep 17 00:00:00 2001 From: Valentin Semirulnik Date: Sat, 16 Jan 2021 01:21:56 +0300 Subject: [PATCH 1/7] Optimizing Sorting - Argument Adaptation --- lib/quick-sort.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/quick-sort.js b/lib/quick-sort.js index 6a7caadb..76e23854 100644 --- a/lib/quick-sort.js +++ b/lib/quick-sort.js @@ -85,7 +85,7 @@ function doQuickSort(ary, comparator, p, r) { // // * Every element in `ary[i+1 .. j-1]` is greater than the pivot. for (var j = p; j < r; j++) { - if (comparator(ary[j], pivot) <= 0) { + if (comparator(ary[j], pivot, false) <= 0) { i += 1; swap(ary, i, j); } From 11e383f71e0b90a067de69529de204b8a47b61f4 Mon Sep 17 00:00:00 2001 From: Valentin Semirulnik Date: Sat, 16 Jan 2021 01:22:34 +0300 Subject: [PATCH 2/7] Optimizing Sorting - Monomorphisation --- lib/quick-sort.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/lib/quick-sort.js b/lib/quick-sort.js index 76e23854..f919a17f 100644 --- a/lib/quick-sort.js +++ b/lib/quick-sort.js @@ -15,6 +15,8 @@ // sorting in C++. By using our own JS-implemented Quick Sort (below), we get // a ~3500ms mean speed-up in `bench/bench.html`. +function SortTemplate(comparator) { + /** * Swap the elements indexed by `x` and `y` in the array `ary`. * @@ -101,6 +103,15 @@ function doQuickSort(ary, comparator, p, r) { } } + return doQuickSort; +} + +function cloneSort(comparator) { + let template = SortTemplate.toString(); + let templateFn = new Function(`return ${template}`)(); + return templateFn(comparator); // Invoke template to get doQuickSort +} + /** * Sort the given array in-place with the given comparator function. * @@ -109,6 +120,13 @@ function doQuickSort(ary, comparator, p, r) { * @param {function} comparator * Function to use to compare two items. */ + +let sortCache = new WeakMap(); // Cache for specialized sorts. exports.quickSort = function (ary, comparator) { + let doQuickSort = sortCache.get(comparator); + if (doQuickSort === void 0) { + doQuickSort = cloneSort(comparator); + sortCache.set(comparator, doQuickSort); + } doQuickSort(ary, comparator, 0, ary.length - 1); }; From e28136cb5b2b4ed796ff9468f3b70e17c6d5cad1 Mon Sep 17 00:00:00 2001 From: Valentin Semirulnik Date: Sat, 16 Jan 2021 00:35:05 +0300 Subject: [PATCH 3/7] optimize originalMappings sorting --- lib/source-map-consumer.js | 21 ++++++++++++++++++--- lib/util.js | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/lib/source-map-consumer.js b/lib/source-map-consumer.js index 7b99d1da..32b828a4 100644 --- a/lib/source-map-consumer.js +++ b/lib/source-map-consumer.js @@ -556,7 +556,16 @@ BasicSourceMapConsumer.prototype._parseMappings = generatedMappings.push(mapping); if (typeof mapping.originalLine === 'number') { - originalMappings.push(mapping); + // This code used to just do: originalMappings.push(mapping). + // Now it sorts original mappings already by source during parsing. + let currentSource = mapping.source; + while (originalMappings.length <= currentSource) { + originalMappings.push(null); + } + if (originalMappings[currentSource] === null) { + originalMappings[currentSource] = []; + } + originalMappings[currentSource].push(mapping); } } } @@ -564,8 +573,14 @@ BasicSourceMapConsumer.prototype._parseMappings = quickSort(generatedMappings, util.compareByGeneratedPositionsDeflated); this.__generatedMappings = generatedMappings; - quickSort(originalMappings, util.compareByOriginalPositions); - this.__originalMappings = originalMappings; + // The code used to sort the whole array: + // quickSort(originalMappings, util.compareByOriginalPositions); + for (var i = 0; i < originalMappings.length; i++) { + if (originalMappings[i] != null) { + quickSort(originalMappings[i], util.compareByOriginalPositionsNoSource); + } + } + this.__originalMappings = [].concat(...originalMappings); }; /** diff --git a/lib/util.js b/lib/util.js index 94e85765..f8d0ad1c 100644 --- a/lib/util.js +++ b/lib/util.js @@ -385,6 +385,38 @@ function compareByOriginalPositions(mappingA, mappingB, onlyCompareOriginal) { } exports.compareByOriginalPositions = compareByOriginalPositions; +function compareByOriginalPositionsNoSource(mappingA, mappingB, onlyCompareOriginal) { + // var cmp = strcmp(mappingA.source, mappingB.source); + // if (cmp !== 0) { + // return cmp; + // } + + var cmp + + cmp = mappingA.originalLine - mappingB.originalLine; + if (cmp !== 0) { + return cmp; + } + + cmp = mappingA.originalColumn - mappingB.originalColumn; + if (cmp !== 0 || onlyCompareOriginal) { + return cmp; + } + + cmp = mappingA.generatedColumn - mappingB.generatedColumn; + if (cmp !== 0) { + return cmp; + } + + cmp = mappingA.generatedLine - mappingB.generatedLine; + if (cmp !== 0) { + return cmp; + } + + return strcmp(mappingA.name, mappingB.name); +} +exports.compareByOriginalPositionsNoSource = compareByOriginalPositionsNoSource; + /** * Comparator between two mappings with deflated source and name indices where * the generated positions are compared. From d76e728c00ef30fa89a813caf203bda5db16bf55 Mon Sep 17 00:00:00 2001 From: Valentin Semirulnik Date: Sat, 16 Jan 2021 01:55:27 +0300 Subject: [PATCH 4/7] Removing Segment Cache --- lib/source-map-consumer.js | 36 ++++++++++++------------------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/lib/source-map-consumer.js b/lib/source-map-consumer.js index 32b828a4..75857244 100644 --- a/lib/source-map-consumer.js +++ b/lib/source-map-consumer.js @@ -493,11 +493,6 @@ BasicSourceMapConsumer.prototype._parseMappings = mapping = new Mapping(); mapping.generatedLine = generatedLine; - // Because each offset is encoded relative to the previous one, - // many segments often have the same encoding. We can exploit this - // fact by caching the parsed variable length fields of each segment, - // allowing us to avoid a second parse if we encounter the same - // segment again. for (end = index; end < length; end++) { if (this._charIsMappingSeparator(aStr, end)) { break; @@ -505,27 +500,20 @@ BasicSourceMapConsumer.prototype._parseMappings = } str = aStr.slice(index, end); - segment = cachedSegments[str]; - if (segment) { - index += str.length; - } else { - segment = []; - while (index < end) { - base64VLQ.decode(aStr, index, temp); - value = temp.value; - index = temp.rest; - segment.push(value); - } - - if (segment.length === 2) { - throw new Error('Found a source, but no line and column'); - } + segment = []; + while (index < end) { + base64VLQ.decode(aStr, index, temp); + value = temp.value; + index = temp.rest; + segment.push(value); + } - if (segment.length === 3) { - throw new Error('Found a source and line, but no column'); - } + if (segment.length === 2) { + throw new Error('Found a source, but no line and column'); + } - cachedSegments[str] = segment; + if (segment.length === 3) { + throw new Error('Found a source and line, but no column'); } // Generated column. From 55dd17c7f32036e0d32c43aeb5c80e28dae1cee7 Mon Sep 17 00:00:00 2001 From: Valentin Semirulnik Date: Sat, 16 Jan 2021 02:07:05 +0300 Subject: [PATCH 5/7] Optimizing generatedMappings Sorting --- lib/quick-sort.js | 4 ++-- lib/source-map-consumer.js | 36 +++++++++++++++++++++++++++++++++++- lib/util.js | 25 +++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 3 deletions(-) diff --git a/lib/quick-sort.js b/lib/quick-sort.js index f919a17f..401f982a 100644 --- a/lib/quick-sort.js +++ b/lib/quick-sort.js @@ -122,11 +122,11 @@ function cloneSort(comparator) { */ let sortCache = new WeakMap(); // Cache for specialized sorts. -exports.quickSort = function (ary, comparator) { +exports.quickSort = function (ary, comparator, start = 0) { let doQuickSort = sortCache.get(comparator); if (doQuickSort === void 0) { doQuickSort = cloneSort(comparator); sortCache.set(comparator, doQuickSort); } - doQuickSort(ary, comparator, 0, ary.length - 1); + doQuickSort(ary, comparator, start, ary.length - 1); }; diff --git a/lib/source-map-consumer.js b/lib/source-map-consumer.js index 75857244..8e020fe0 100644 --- a/lib/source-map-consumer.js +++ b/lib/source-map-consumer.js @@ -464,6 +464,36 @@ function Mapping() { * query (the ordered arrays in the `this.__generatedMappings` and * `this.__originalMappings` properties). */ + +const compareGenerated = util.compareByGeneratedPositionsDeflatedNoLine; +function sortGenerated(array, start) { + let l = array.length; + let n = array.length - start; + if (n <= 1) { + return; + } else if (n == 2) { + let a = array[start]; + let b = array[start + 1]; + if (compareGenerated(a, b) > 0) { + array[start] = b; + array[start + 1] = a; + } + } else if (n < 20) { + for (let i = start; i < l; i++) { + for (let j = i; j > start; j--) { + let a = array[j - 1]; + let b = array[j]; + if (compareGenerated(a, b) <= 0) { + break; + } + array[j - 1] = b; + array[j] = a; + } + } + } else { + quickSort(array, compareGenerated, start); + } +} BasicSourceMapConsumer.prototype._parseMappings = function SourceMapConsumer_parseMappings(aStr, aSourceRoot) { var generatedLine = 1; @@ -480,11 +510,15 @@ BasicSourceMapConsumer.prototype._parseMappings = var generatedMappings = []; var mapping, str, segment, end, value; + let subarrayStart = 0; while (index < length) { if (aStr.charAt(index) === ';') { generatedLine++; index++; previousGeneratedColumn = 0; + + sortGenerated(generatedMappings, subarrayStart); + subarrayStart = generatedMappings.length; } else if (aStr.charAt(index) === ',') { index++; @@ -558,7 +592,7 @@ BasicSourceMapConsumer.prototype._parseMappings = } } - quickSort(generatedMappings, util.compareByGeneratedPositionsDeflated); + sortGenerated(generatedMappings, subarrayStart); this.__generatedMappings = generatedMappings; // The code used to sort the whole array: diff --git a/lib/util.js b/lib/util.js index f8d0ad1c..0c744a1c 100644 --- a/lib/util.js +++ b/lib/util.js @@ -456,6 +456,31 @@ function compareByGeneratedPositionsDeflated(mappingA, mappingB, onlyCompareGene } exports.compareByGeneratedPositionsDeflated = compareByGeneratedPositionsDeflated; +function compareByGeneratedPositionsDeflatedNoLine(mappingA, mappingB, onlyCompareGenerated) { + var cmp = mappingA.generatedColumn - mappingB.generatedColumn; + if (cmp !== 0 || onlyCompareGenerated) { + return cmp; + } + + cmp = strcmp(mappingA.source, mappingB.source); + if (cmp !== 0) { + return cmp; + } + + cmp = mappingA.originalLine - mappingB.originalLine; + if (cmp !== 0) { + return cmp; + } + + cmp = mappingA.originalColumn - mappingB.originalColumn; + if (cmp !== 0) { + return cmp; + } + + return strcmp(mappingA.name, mappingB.name); +} +exports.compareByGeneratedPositionsDeflatedNoLine = compareByGeneratedPositionsDeflatedNoLine; + function strcmp(aStr1, aStr2) { if (aStr1 === aStr2) { return 0; From 3b2aaeb5f2186caca6ef0e7b8ca63c0338eaf39a Mon Sep 17 00:00:00 2001 From: Valentin Semirulnik Date: Mon, 8 Feb 2021 00:36:23 +0300 Subject: [PATCH 6/7] Remove comments --- lib/quick-sort.js | 4 ++-- lib/source-map-consumer.js | 4 ---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/quick-sort.js b/lib/quick-sort.js index 401f982a..23f9eda5 100644 --- a/lib/quick-sort.js +++ b/lib/quick-sort.js @@ -109,7 +109,7 @@ function doQuickSort(ary, comparator, p, r) { function cloneSort(comparator) { let template = SortTemplate.toString(); let templateFn = new Function(`return ${template}`)(); - return templateFn(comparator); // Invoke template to get doQuickSort + return templateFn(comparator); } /** @@ -121,7 +121,7 @@ function cloneSort(comparator) { * Function to use to compare two items. */ -let sortCache = new WeakMap(); // Cache for specialized sorts. +let sortCache = new WeakMap(); exports.quickSort = function (ary, comparator, start = 0) { let doQuickSort = sortCache.get(comparator); if (doQuickSort === void 0) { diff --git a/lib/source-map-consumer.js b/lib/source-map-consumer.js index 8e020fe0..17bd8c3f 100644 --- a/lib/source-map-consumer.js +++ b/lib/source-map-consumer.js @@ -578,8 +578,6 @@ BasicSourceMapConsumer.prototype._parseMappings = generatedMappings.push(mapping); if (typeof mapping.originalLine === 'number') { - // This code used to just do: originalMappings.push(mapping). - // Now it sorts original mappings already by source during parsing. let currentSource = mapping.source; while (originalMappings.length <= currentSource) { originalMappings.push(null); @@ -595,8 +593,6 @@ BasicSourceMapConsumer.prototype._parseMappings = sortGenerated(generatedMappings, subarrayStart); this.__generatedMappings = generatedMappings; - // The code used to sort the whole array: - // quickSort(originalMappings, util.compareByOriginalPositions); for (var i = 0; i < originalMappings.length; i++) { if (originalMappings[i] != null) { quickSort(originalMappings[i], util.compareByOriginalPositionsNoSource); From 42c9cf42ee81dc224c6db8be8dc405c1c241df82 Mon Sep 17 00:00:00 2001 From: Valentin Semirulnik Date: Mon, 8 Feb 2021 00:46:27 +0300 Subject: [PATCH 7/7] Remove useless block --- lib/util.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/util.js b/lib/util.js index 0c744a1c..430e2d0f 100644 --- a/lib/util.js +++ b/lib/util.js @@ -386,11 +386,6 @@ function compareByOriginalPositions(mappingA, mappingB, onlyCompareOriginal) { exports.compareByOriginalPositions = compareByOriginalPositions; function compareByOriginalPositionsNoSource(mappingA, mappingB, onlyCompareOriginal) { - // var cmp = strcmp(mappingA.source, mappingB.source); - // if (cmp !== 0) { - // return cmp; - // } - var cmp cmp = mappingA.originalLine - mappingB.originalLine;