From 4013c4715e28dba7a7f95f15e0e28f31d3c88baa Mon Sep 17 00:00:00 2001 From: Chris Truter Date: Mon, 5 Jan 2015 15:49:47 +0200 Subject: [PATCH] Replace `_mappings` array with data structure Optimized for best case (called with sorted mappings), with comparable performance otherwise. --- lib/source-map/mapping-list.js | 80 ++++++++++++++++++++++++++ lib/source-map/source-map-consumer.js | 4 +- lib/source-map/source-map-generator.js | 20 +++---- 3 files changed, 90 insertions(+), 14 deletions(-) create mode 100644 lib/source-map/mapping-list.js diff --git a/lib/source-map/mapping-list.js b/lib/source-map/mapping-list.js new file mode 100644 index 00000000..e698a5d2 --- /dev/null +++ b/lib/source-map/mapping-list.js @@ -0,0 +1,80 @@ +/* -*- Mode: js; js-indent-level: 2; -*- */ +/* + * Copyright 2014 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + */ +if (typeof define !== 'function') { + var define = require('amdefine')(module, require); +} +define(function (require, exports, module) { + + var util = require('./util'); + + /** + * Determine whether mappingB is after mappingA with respect to generated + * position. + */ + function generatedPositionAfter(mappingA, mappingB) { + // Optimized for most common case + var lineA = mappingA.generatedLine; + var lineB = mappingB.generatedLine; + var columnA = mappingA.generatedColumn; + var columnB = mappingB.generatedColumn; + return lineB > lineA || lineB == lineA && columnB >= columnA || + util.compareByGeneratedPositions(mappingA, mappingB) <= 0; + } + + /** + * A data structure to provide a sorted view of accumulated mappings in a + * performance conscious manner. It trades a neglibable overhead in general + * case for a large speedup in case of mappings being added in order. + */ + function MappingList() { + this._array = []; + this._sorted = true; + // Serves as infimum + this._last = {generatedLine: -1, generatedColumn: 0}; + } + + /** + * Iterate through internal items. NOTE: order is NOT guaranteed. + */ + MappingList.prototype.unsortedForEach = function MappingList_forEach() { + Array.prototype.forEach.apply(this._array, arguments); + }; + + /** + * Add the given source mapping. + * + * @param Object aMapping + */ + MappingList.prototype.add = function MappingList_add(aMapping) { + var mapping; + if (generatedPositionAfter(this._last, aMapping)) { + this._last = aMapping; + this._array.push(aMapping); + } else { + this._sorted = false; + this._array.push(aMapping); + } + }; + + /** + * Returns the flat array representation of this structure. + * + * WARNING: Returns internal data without copying, for performance + */ + MappingList.prototype.toArray = function MappingList_toArray() { + if (this._sorted) { + return this._array; + } else { + // Sort runs in place + this._sorted = true; + return this._array.sort(util.compareByGeneratedPositions); + } + }; + + exports.MappingList = MappingList; + +}); diff --git a/lib/source-map/source-map-consumer.js b/lib/source-map/source-map-consumer.js index a33327fe..2f811122 100644 --- a/lib/source-map/source-map-consumer.js +++ b/lib/source-map/source-map-consumer.js @@ -102,9 +102,9 @@ define(function (require, exports, module) { smc.sourceRoot); smc.file = aSourceMap._file; - smc.__generatedMappings = aSourceMap._mappings.slice() + smc.__generatedMappings = aSourceMap._mappings.toArray() .sort(util.compareByGeneratedPositions); - smc.__originalMappings = aSourceMap._mappings.slice() + smc.__originalMappings = aSourceMap._mappings.toArray() .sort(util.compareByOriginalPositions); return smc; diff --git a/lib/source-map/source-map-generator.js b/lib/source-map/source-map-generator.js index 5387fa1d..391f7335 100644 --- a/lib/source-map/source-map-generator.js +++ b/lib/source-map/source-map-generator.js @@ -12,6 +12,7 @@ define(function (require, exports, module) { var base64VLQ = require('./base64-vlq'); var util = require('./util'); var ArraySet = require('./array-set').ArraySet; + var MappingList = require('./mapping-list').MappingList; /** * An instance of the SourceMapGenerator represents a source map which is @@ -29,7 +30,7 @@ define(function (require, exports, module) { this._sourceRoot = util.getArg(aArgs, 'sourceRoot', null); this._sources = new ArraySet(); this._names = new ArraySet(); - this._mappings = []; + this._mappings = new MappingList(); this._sourcesContents = null; } @@ -109,7 +110,7 @@ define(function (require, exports, module) { this._names.add(name); } - this._mappings.push({ + this._mappings.add({ generatedLine: generated.line, generatedColumn: generated.column, originalLine: original != null && original.line, @@ -186,7 +187,7 @@ define(function (require, exports, module) { var newNames = new ArraySet(); // Find mappings for the "sourceFile" - this._mappings.forEach(function (mapping) { + this._mappings.unsortedForEach(function (mapping) { if (mapping.source === sourceFile && mapping.originalLine != null) { // Check if it can be mapped by the source map, then update the mapping. var original = aSourceMapConsumer.originalPositionFor({ @@ -292,15 +293,10 @@ define(function (require, exports, module) { var result = ''; var mapping; - // The mappings must be guaranteed to be in sorted order before we start - // serializing them or else the generated line numbers (which are defined - // via the ';' separators) will be all messed up. Note: it might be more - // performant to maintain the sorting as we insert them, rather than as we - // serialize them, but the big O is the same either way. - this._mappings.sort(util.compareByGeneratedPositions); + var mappings = this._mappings.toArray(); - for (var i = 0, len = this._mappings.length; i < len; i++) { - mapping = this._mappings[i]; + for (var i = 0, len = mappings.length; i < len; i++) { + mapping = mappings[i]; if (mapping.generatedLine !== previousGeneratedLine) { previousGeneratedColumn = 0; @@ -311,7 +307,7 @@ define(function (require, exports, module) { } else { if (i > 0) { - if (!util.compareByGeneratedPositions(mapping, this._mappings[i - 1])) { + if (!util.compareByGeneratedPositions(mapping, mappings[i - 1])) { continue; } result += ',';