Merge pull request #494 from gaearon/perf-2

Performance impovements
This commit is contained in:
Sebastian McKenzie 2015-01-15 19:28:04 +11:00
commit 488b719fde
4 changed files with 167 additions and 81 deletions

View File

@ -67,32 +67,57 @@ Buffer.prototype.removeLast = function (cha) {
Buffer.prototype.newline = function (i, removeLast) { Buffer.prototype.newline = function (i, removeLast) {
if (this.format.compact) return; if (this.format.compact) return;
removeLast = removeLast || false;
if (_.isBoolean(i)) {
removeLast = i;
i = null;
}
if (_.isNumber(i)) { if (_.isNumber(i)) {
if (this.endsWith("{\n")) i--; if (this.endsWith("{\n")) i--;
if (this.endsWith(util.repeat(i, "\n"))) return; if (this.endsWith(util.repeat(i, "\n"))) return;
for (var j = 0; j < i; j++) { while (i--) {
this.newline(null, removeLast); this._newline(removeLast);
} }
return; return;
} }
if (_.isBoolean(i)) {
removeLast = i;
}
this._newline(removeLast);
};
Buffer.prototype._newline = function (removeLast) {
if (removeLast && this.isLast("\n")) this.removeLast("\n"); if (removeLast && this.isLast("\n")) this.removeLast("\n");
this.removeLast(" "); this.removeLast(" ");
// remove whitespace if last character was a newline // remove whitespace if last character was a newline
this.buf = this.buf.replace(/\n +$/, "\n"); this._removeSpacesAfterLastNewline();
this._push("\n"); this._push("\n");
}; };
/**
* If buffer ends with a newline and some spaces after it, trim those spaces.
*/
Buffer.prototype._removeSpacesAfterLastNewline = function () {
var lastNewlineIndex = this.buf.lastIndexOf('\n');
if (lastNewlineIndex === -1)
return;
var index = this.buf.length - 1;
while (index > lastNewlineIndex) {
if (this.buf[index] !== ' ') {
break;
}
index--;
}
if (index === lastNewlineIndex) {
this.buf = this.buf.substring(0, index + 1);
}
};
Buffer.prototype.push = function (str, noIndent) { Buffer.prototype.push = function (str, noIndent) {
if (this._indent && !noIndent && str !== "\n") { if (this._indent && !noIndent && str !== "\n") {
// we have an indent level and we aren't pushing a newline // we have an indent level and we aren't pushing a newline

View File

@ -13,7 +13,7 @@ var find = function (obj, node, parent) {
for (var i = 0; i < types.length; i++) { for (var i = 0; i < types.length; i++) {
var type = types[i]; var type = types[i];
if (t["is" + type](node)) { if (t.is(type, node)) {
var fn = obj[type]; var fn = obj[type];
result = fn(node, parent); result = fn(node, parent);
if (result != null) break; if (result != null) break;
@ -28,13 +28,11 @@ function Node(node, parent) {
this.node = node; this.node = node;
} }
Node.prototype.isUserWhitespacable = function () { Node.isUserWhitespacable = function (node) {
return t.isUserWhitespacable(this.node); return t.isUserWhitespacable(node);
}; };
Node.prototype.needsWhitespace = function (type) { Node.needsWhitespace = function (node, parent, type) {
var parent = this.parent;
var node = this.node;
if (!node) return 0; if (!node) return 0;
if (t.isExpressionStatement(node)) { if (t.isExpressionStatement(node)) {
@ -51,18 +49,15 @@ Node.prototype.needsWhitespace = function (type) {
return lines || 0; return lines || 0;
}; };
Node.prototype.needsWhitespaceBefore = function () { Node.needsWhitespaceBefore = function (node, parent) {
return this.needsWhitespace("before"); return Node.needsWhitespace(node, parent, "before");
}; };
Node.prototype.needsWhitespaceAfter = function () { Node.needsWhitespaceAfter = function (node, parent) {
return this.needsWhitespace("after"); return Node.needsWhitespace(node, parent, "after");
}; };
Node.prototype.needsParens = function () { Node.needsParens = function (node, parent) {
var parent = this.parent;
var node = this.node;
if (!parent) return false; if (!parent) return false;
if (t.isNewExpression(parent) && parent.callee === node) { if (t.isNewExpression(parent) && parent.callee === node) {
@ -77,10 +72,7 @@ Node.prototype.needsParens = function () {
return find(parens, node, parent); return find(parens, node, parent);
}; };
Node.prototype.needsParensNoLineTerminator = function () { Node.needsParensNoLineTerminator = function (node, parent) {
var parent = this.parent;
var node = this.node;
if (!parent) return false; if (!parent) return false;
// no comments // no comments
@ -100,17 +92,18 @@ Node.prototype.needsParensNoLineTerminator = function () {
return false; return false;
}; };
_.each(Node.prototype, function (fn, key) { _.each(Node, function (fn, key) {
Node[key] = function (node, parent) { Node.prototype[key] = function () {
var n = new Node(node, parent);
// Avoid leaking arguments to prevent deoptimization // Avoid leaking arguments to prevent deoptimization
var skipCount = 2; var args = new Array(arguments.length + 2);
var args = new Array(arguments.length - skipCount);
args[0] = this.node;
args[1] = this.parent;
for (var i = 0; i < args.length; i++) { for (var i = 0; i < args.length; i++) {
args[i] = arguments[i + 2]; args[i + 2] = arguments[i];
} }
return n[key].apply(n, args); return Node[key].apply(null, args);
}; };
}); });

View File

@ -2,9 +2,37 @@ module.exports = Whitespace;
var _ = require("lodash"); var _ = require("lodash");
/**
* Returns `i`th number from `base`, continuing from 0 when `max` is reached.
* Useful for shifting `for` loop by a fixed number but going over all items.
*
* @param {Number} i Current index in the loop
* @param {Number} base Start index for which to return 0
* @param {Number} max Array length
* @returns {Number} shiftedIndex
*/
function getLookupIndex(i, base, max) {
i += base;
if (i >= max)
i -= max;
return i;
}
function Whitespace(tokens, comments) { function Whitespace(tokens, comments) {
this.tokens = _.sortBy(tokens.concat(comments), "start"); this.tokens = _.sortBy(tokens.concat(comments), "start");
this.used = []; this.used = {};
// Profiling this code shows that while generator passes over it, indexes
// returned by `getNewlinesBefore` and `getNewlinesAfter` are always increasing.
// We use this implementation detail for an optimization: instead of always
// starting to look from `this.tokens[0]`, we will start `for` loops from the
// previous successful match. We will enumerate all tokens—but the common
// case will be much faster.
this._lastFoundIndex = 0;
} }
Whitespace.prototype.getNewlinesBefore = function (node) { Whitespace.prototype.getNewlinesBefore = function (node) {
@ -13,13 +41,17 @@ Whitespace.prototype.getNewlinesBefore = function (node) {
var tokens = this.tokens; var tokens = this.tokens;
var token; var token;
for (var i = 0; i < tokens.length; i++) { for (var j = 0; j < tokens.length; j++) {
// optimize for forward traversal by shifting for loop index
var i = getLookupIndex(j, this._lastFoundIndex, this.tokens.length);
token = tokens[i]; token = tokens[i];
// this is the token this node starts with // this is the token this node starts with
if (node.start === token.start) { if (node.start === token.start) {
startToken = tokens[i - 1]; startToken = tokens[i - 1];
endToken = token; endToken = token;
this._lastFoundIndex = i;
break; break;
} }
} }
@ -33,13 +65,17 @@ Whitespace.prototype.getNewlinesAfter = function (node) {
var tokens = this.tokens; var tokens = this.tokens;
var token; var token;
for (var i = 0; i < tokens.length; i++) { for (var j = 0; j < tokens.length; j++) {
// optimize for forward traversal by shifting for loop index
var i = getLookupIndex(j, this._lastFoundIndex, this.tokens.length);
token = tokens[i]; token = tokens[i];
// this is the token this node ends with // this is the token this node ends with
if (node.end === token.end) { if (node.end === token.end) {
startToken = token; startToken = token;
endToken = tokens[i + 1]; endToken = tokens[i + 1];
this._lastFoundIndex = i;
break; break;
} }
} }
@ -60,12 +96,11 @@ Whitespace.prototype.getNewlinesAfter = function (node) {
Whitespace.prototype.getNewlinesBetween = function (startToken, endToken) { Whitespace.prototype.getNewlinesBetween = function (startToken, endToken) {
var start = startToken ? startToken.loc.end.line : 1; var start = startToken ? startToken.loc.end.line : 1;
var end = endToken.loc.start.line; var end = endToken.loc.start.line;
var lines = 0; var lines = 0;
for (var line = start; line < end; line++) { for (var line = start; line < end; line++) {
if (!_.contains(this.used, line)) { if (typeof this.used[line] === 'undefined') {
this.used.push(line); this.used[line] = true;
lines++; lines++;
} }
} }

View File

@ -7,14 +7,25 @@ t.NATIVE_TYPE_NAMES = ["Array", "Object", "Number", "Boolean", "Date", "Array",
// //
var addAssert = function (type, is) { /**
* Registers `is[Type]` and `assert[Type]` generated functions for a given `type`.
* Pass `skipAliasCheck` to force it to directly compare `node.type` with `type`.
*
* @param {String} type
* @param {Boolean?} skipAliasCheck
*/
function registerType(type, skipAliasCheck) {
var is = t["is" + type] = function (node, opts) {
return t.is(type, node, opts, skipAliasCheck);
};
t["assert" + type] = function (node, opts) { t["assert" + type] = function (node, opts) {
opts = opts || {}; opts = opts || {};
if (!is(node, opts)) { if (!is(node, opts)) {
throw new Error("Expected type " + JSON.stringify(type) + " with option " + JSON.stringify(opts)); throw new Error("Expected type " + JSON.stringify(type) + " with option " + JSON.stringify(opts));
} }
}; };
}; }
t.STATEMENT_OR_BLOCK_KEYS = ["consequent", "body"]; t.STATEMENT_OR_BLOCK_KEYS = ["consequent", "body"];
@ -22,14 +33,59 @@ t.STATEMENT_OR_BLOCK_KEYS = ["consequent", "body"];
t.VISITOR_KEYS = require("./visitor-keys"); t.VISITOR_KEYS = require("./visitor-keys");
_.each(t.VISITOR_KEYS, function (keys, type) { t.ALIAS_KEYS = require("./alias-keys");
var is = t["is" + type] = function (node, opts) {
return node && node.type === type && t.shallowEqual(node, opts);
};
addAssert(type, is); t.FLIPPED_ALIAS_KEYS = {};
_.each(t.VISITOR_KEYS, function (keys, type) {
registerType(type, true);
}); });
_.each(t.ALIAS_KEYS, function (aliases, type) {
_.each(aliases, function (alias) {
var types = t.FLIPPED_ALIAS_KEYS[alias] = t.FLIPPED_ALIAS_KEYS[alias] || [];
types.push(type);
});
});
_.each(t.FLIPPED_ALIAS_KEYS, function (types, type) {
t[type.toUpperCase() + "_TYPES"] = types;
registerType(type, false);
});
/**
* Returns whether `node` is of given `type`.
* For better performance, use this instead of `is[Type]` when `type` is unknown.
* Optionally, pass `skipAliasCheck` to directly compare `node.type` with `type`.
*
* @param {String} type
* @param {Node} node
* @param {Object?} opts
* @param {Boolean?} skipAliasCheck
* @returns {Boolean} isOfType
*/
t.is = function (type, node, opts, skipAliasCheck) {
if (!node) return;
var typeMatches = (type === node.type);
if (!typeMatches && !skipAliasCheck) {
var aliases = t.FLIPPED_ALIAS_KEYS[type];
if (typeof aliases !== 'undefined')
typeMatches = aliases.indexOf(node.type) > -1;
}
if (!typeMatches) {
return false;
}
if (typeof opts !== 'undefined')
return t.shallowEqual(node, opts);
return true;
};
// //
t.BUILDER_KEYS = _.defaults(require("./builder-keys"), t.VISITOR_KEYS); t.BUILDER_KEYS = _.defaults(require("./builder-keys"), t.VISITOR_KEYS);
@ -45,29 +101,6 @@ _.each(t.BUILDER_KEYS, function (keys, type) {
}; };
}); });
//
t.ALIAS_KEYS = require("./alias-keys");
t.FLIPPED_ALIAS_KEYS = {};
_.each(t.ALIAS_KEYS, function (aliases, type) {
_.each(aliases, function (alias) {
var types = t.FLIPPED_ALIAS_KEYS[alias] = t.FLIPPED_ALIAS_KEYS[alias] || [];
types.push(type);
});
});
_.each(t.FLIPPED_ALIAS_KEYS, function (types, type) {
t[type.toUpperCase() + "_TYPES"] = types;
var is = t["is" + type] = function (node, opts) {
return node && types.indexOf(node.type) >= 0 && t.shallowEqual(node, opts);
};
addAssert(type, is);
});
/** /**
* Description * Description
* *
@ -139,17 +172,17 @@ t.toSequenceExpression = function (nodes, scope) {
// //
t.shallowEqual = function (actual, expected) { t.shallowEqual = function (actual, expected) {
var same = true; var keys = Object.keys(expected);
var key;
if (expected) { for (var i = 0; i < keys.length; i++) {
_.each(expected, function (val, key) { key = keys[i];
if (actual[key] !== val) {
return same = false; if (actual[key] !== expected[key])
} return false;
});
} }
return same; return true;
}; };
/** /**