Skip to content

Instantly share code, notes, and snippets.

@jridgewell
Last active July 9, 2019 03:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jridgewell/cf3bd9c486e6303d2a329fe4a3be5735 to your computer and use it in GitHub Desktop.
Save jridgewell/cf3bd9c486e6303d2a329fe4a3be5735 to your computer and use it in GitHub Desktop.
Decoding a SourceMap (https://jsbench.github.io/#cf3bd9c486e6303d2a329fe4a3be5735) #jsbench #jsperf
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Decoding a SourceMap</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/benchmark/1.0.0/benchmark.min.js"></script>
<script src="./suite.js"></script>
</head>
<body>
<h1>Open the console to view the results</h1>
<h2><code>cmd + alt + j</code> or <code>ctrl + alt + j</code></h2>
</body>
</html>
"use strict";
(function (factory) {
if (typeof Benchmark !== "undefined") {
factory(Benchmark);
} else {
factory(require("benchmark"));
}
})(function (Benchmark) {
var suite = new Benchmark.Suite;
Benchmark.prototype.setup = function () {
const map = {"version":3,"file":"sourcemap-codec.umd.js","sources":["../src/sourcemap-codec.ts"],"sourcesContent":["export type SourceMapSegment =\n\t| [number]\n\t| [number, number, number, number]\n\t| [number, number, number, number, number];\nexport type SourceMapLine = SourceMapSegment[];\nexport type SourceMapMappings = SourceMapLine[];\n\nconst charToInteger: { [charCode: number]: number } = {};\nconst chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';\n\nfor (let i = 0; i < chars.length; i++) {\n\tcharToInteger[chars.charCodeAt(i)] = i;\n}\n\nexport function decode(mappings: string): SourceMapMappings {\n\tlet generatedCodeColumn = 0; // first field\n\tlet sourceFileIndex = 0; // second field\n\tlet sourceCodeLine = 0; // third field\n\tlet sourceCodeColumn = 0; // fourth field\n\tlet nameIndex = 0; // fifth field\n\n\tconst decoded: SourceMapMappings = [];\n\tlet line: SourceMapLine = [];\n\tlet segment: number[] = [];\n\n\tfor (let i = 0, j = 0, shift = 0, value = 0, len = mappings.length; i < len; i++) {\n\t\tconst c = mappings.charCodeAt(i);\n\n\t\tif (c === 44) { // \",\"\n\t\t\tif (segment.length) line.push(segment as SourceMapSegment);\n\t\t\tsegment = [];\n\t\t\tj = 0;\n\n\t\t} else if (c === 59) { // \";\"\n\t\t\tif (segment.length) line.push(segment as SourceMapSegment);\n\t\t\tsegment = [];\n\t\t\tj = 0;\n\t\t\tdecoded.push(line);\n\t\t\tline = [];\n\t\t\tgeneratedCodeColumn = 0;\n\n\t\t} else {\n\t\t\tlet integer = charToInteger[c];\n\t\t\tif (integer === undefined) {\n\t\t\t\tthrow new Error('Invalid character (' + String.fromCharCode(c) + ')');\n\t\t\t}\n\n\t\t\tconst hasContinuationBit = integer & 32;\n\n\t\t\tinteger &= 31;\n\t\t\tvalue += integer << shift;\n\n\t\t\tif (hasContinuationBit) {\n\t\t\t\tshift += 5;\n\t\t\t} else {\n\t\t\t\tconst shouldNegate = value & 1;\n\t\t\t\tvalue >>>= 1;\n\n\t\t\t\tif (shouldNegate) {\n\t\t\t\t\tvalue = -value;\n\t\t\t\t\tif (value === 0) value = -0x80000000;\n\t\t\t\t}\n\n\t\t\t\tif (j == 0) {\n\t\t\t\t\tgeneratedCodeColumn += value;\n\t\t\t\t\tsegment.push(generatedCodeColumn);\n\n\t\t\t\t} else if (j === 1) {\n\t\t\t\t\tsourceFileIndex += value;\n\t\t\t\t\tsegment.push(sourceFileIndex);\n\n\t\t\t\t} else if (j === 2) {\n\t\t\t\t\tsourceCodeLine += value;\n\t\t\t\t\tsegment.push(sourceCodeLine);\n\n\t\t\t\t} else if (j === 3) {\n\t\t\t\t\tsourceCodeColumn += value;\n\t\t\t\t\tsegment.push(sourceCodeColumn);\n\n\t\t\t\t} else if (j === 4) {\n\t\t\t\t\tnameIndex += value;\n\t\t\t\t\tsegment.push(nameIndex);\n\t\t\t\t}\n\n\t\t\t\tj++;\n\t\t\t\tvalue = shift = 0; // reset\n\t\t\t}\n\t\t}\n\t}\n\n\tif (segment.length) line.push(segment as SourceMapSegment);\n\tdecoded.push(line);\n\n\treturn decoded;\n}\n\nexport function encode(decoded: SourceMapMappings): string {\n\tlet sourceFileIndex = 0; // second field\n\tlet sourceCodeLine = 0; // third field\n\tlet sourceCodeColumn = 0; // fourth field\n\tlet nameIndex = 0; // fifth field\n\tlet mappings = '';\n\n\tfor (let i = 0; i < decoded.length; i++) {\n\t\tconst line = decoded[i];\n\t\tif (i > 0) mappings += ';';\n\t\tif (line.length === 0) continue;\n\n\t\tlet generatedCodeColumn = 0; // first field\n\n\t\tconst lineMappings: string[] = [];\n\n\t\tfor (const segment of line) {\n\t\t\tlet segmentMappings = encodeInteger(segment[0] - generatedCodeColumn);\n\t\t\tgeneratedCodeColumn = segment[0];\n\n\t\t\tif (segment.length > 1) {\n\t\t\t\tsegmentMappings +=\n\t\t\t\t\tencodeInteger(segment[1] - sourceFileIndex) +\n\t\t\t\t\tencodeInteger(segment[2] - sourceCodeLine) +\n\t\t\t\t\tencodeInteger(segment[3] - sourceCodeColumn);\n\n\t\t\t\tsourceFileIndex = segment[1];\n\t\t\t\tsourceCodeLine = segment[2];\n\t\t\t\tsourceCodeColumn = segment[3];\n\t\t\t}\n\n\t\t\tif (segment.length === 5) {\n\t\t\t\tsegmentMappings += encodeInteger(segment[4] - nameIndex);\n\t\t\t\tnameIndex = segment[4];\n\t\t\t}\n\n\t\t\tlineMappings.push(segmentMappings);\n\t\t}\n\n\t\tmappings += lineMappings.join(',');\n\t}\n\n\treturn mappings;\n}\n\nfunction encodeInteger(num: number): string {\n\tvar result = '';\n\tnum = num < 0 ? (-num << 1) | 1 : num << 1;\n\tdo {\n\t\tvar clamped = num & 31;\n\t\tnum >>>= 5;\n\t\tif (num > 0) {\n\t\t\tclamped |= 32;\n\t\t}\n\t\tresult += chars[clamped];\n\t} while (num > 0);\n\n\treturn result;\n}\n"],"names":[],"mappings":"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"}
const mappings = map.mappings;
// Current code
(function (exports) { 'use strict';
var charToInteger = {};
var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
for (var i = 0; i < chars.length; i++) {
charToInteger[chars.charCodeAt(i)] = i;
}
function decode(mappings) {
var generatedCodeColumn = 0; // first field
var sourceFileIndex = 0; // second field
var sourceCodeLine = 0; // third field
var sourceCodeColumn = 0; // fourth field
var nameIndex = 0; // fifth field
var decoded = [];
var line = [];
var segment = [];
for (var i = 0, j = 0, shift = 0, value = 0, len = mappings.length; i < len; i++) {
var c = mappings.charCodeAt(i);
if (c === 44) { // ","
if (segment.length)
line.push(segment);
segment = [];
j = 0;
}
else if (c === 59) { // ";"
if (segment.length)
line.push(segment);
segment = [];
j = 0;
decoded.push(line);
line = [];
generatedCodeColumn = 0;
}
else {
var integer = charToInteger[c];
if (integer === undefined) {
throw new Error('Invalid character (' + String.fromCharCode(c) + ')');
}
var hasContinuationBit = integer & 32;
integer &= 31;
value += integer << shift;
if (hasContinuationBit) {
shift += 5;
}
else {
var shouldNegate = value & 1;
value >>>= 1;
if (shouldNegate) {
value = -value;
if (value === 0)
value = -0x80000000;
}
if (j == 0) {
generatedCodeColumn += value;
segment.push(generatedCodeColumn);
}
else if (j === 1) {
sourceFileIndex += value;
segment.push(sourceFileIndex);
}
else if (j === 2) {
sourceCodeLine += value;
segment.push(sourceCodeLine);
}
else if (j === 3) {
sourceCodeColumn += value;
segment.push(sourceCodeColumn);
}
else if (j === 4) {
nameIndex += value;
segment.push(nameIndex);
}
j++;
value = shift = 0; // reset
}
}
}
if (segment.length)
line.push(segment);
decoded.push(line);
return decoded;
}
exports.decode = decode;
})(window.current = {});
//# sourceMappingURL=sourcemap-codec.umd.js.map
// Proposed Code
(function (exports) { 'use strict';
var charToInteger = {};
var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
for (var i = 0; i < chars.length; i++) {
charToInteger[chars.charCodeAt(i)] = i;
}
function decode(mappings) {
var decoded = [];
var line = [];
var segment = [
0,
0,
0,
0,
0,
];
var j = 0;
for (var i = 0, shift = 0, value = 0; i < mappings.length; i++) {
var c = mappings.charCodeAt(i);
if (c === 44) { // ","
segmentify(line, segment, j);
j = 0;
}
else if (c === 59) { // ";"
segmentify(line, segment, j);
j = 0;
decoded.push(line);
line = [];
segment[0] = 0;
}
else {
var integer = charToInteger[c];
if (integer === undefined) {
throw new Error('Invalid character (' + String.fromCharCode(c) + ')');
}
var hasContinuationBit = integer & 32;
integer &= 31;
value += integer << shift;
if (hasContinuationBit) {
shift += 5;
}
else {
var shouldNegate = value & 1;
value >>>= 1;
if (shouldNegate) {
value = value === 0 ? -0x80000000 : -value;
}
segment[j] += value;
j++;
value = shift = 0; // reset
}
}
}
segmentify(line, segment, j);
decoded.push(line);
return decoded;
}
function segmentify(line, segment, j) {
// This looks ugly, but we're creating specialized arrays with a specific
// length. This is much faster than creating a new array (which v8 expands to
// a capacity of 17 after pushing the first item), or slicing out a subarray
// (which is slow). Length 4 is assumed to be the most frequent, followed by
// length 5 (since not everything will have an associated name), followed by
// length 1 (it's probably rare for a source substring to not have an
// associated segment data).
if (j === 4)
line.push([segment[0], segment[1], segment[2], segment[3]]);
else if (j === 5)
line.push([segment[0], segment[1], segment[2], segment[3], segment[4]]);
else if (j === 1)
line.push([segment[0]]);
}
exports.decode = decode;
})(window.proposal = {});
};
suite.add("v1.4.6", function () {
// v1.4.6
current.decode(mappings);
});
suite.add("Proposal (using array literals)", function () {
// Proposal (using array literals)
proposal.decode(mappings);
});
suite.on("cycle", function (evt) {
console.log(" - " + evt.target);
});
suite.on("complete", function (evt) {
console.log(new Array(30).join("-"));
var results = evt.currentTarget.sort(function (a, b) {
return b.hz - a.hz;
});
results.forEach(function (item) {
console.log((idx + 1) + ". " + item);
});
});
console.log("Decoding a SourceMap");
console.log(new Array(30).join("-"));
suite.run();
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment