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) {
if (this.format.compact) return;
if (_.isBoolean(i)) {
removeLast = i;
i = null;
}
removeLast = removeLast || false;
if (_.isNumber(i)) {
if (this.endsWith("{\n")) i--;
if (this.endsWith(util.repeat(i, "\n"))) return;
for (var j = 0; j < i; j++) {
this.newline(null, removeLast);
while (i--) {
this._newline(removeLast);
}
return;
}
if (_.isBoolean(i)) {
removeLast = i;
}
this._newline(removeLast);
};
Buffer.prototype._newline = function (removeLast) {
if (removeLast && this.isLast("\n")) this.removeLast("\n");
this.removeLast(" ");
// remove whitespace if last character was a newline
this.buf = this.buf.replace(/\n +$/, "\n");
this._removeSpacesAfterLastNewline();
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) {
if (this._indent && !noIndent && str !== "\n") {
// 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++) {
var type = types[i];
if (t["is" + type](node)) {
if (t.is(type, node)) {
var fn = obj[type];
result = fn(node, parent);
if (result != null) break;
@ -28,13 +28,11 @@ function Node(node, parent) {
this.node = node;
}
Node.prototype.isUserWhitespacable = function () {
return t.isUserWhitespacable(this.node);
Node.isUserWhitespacable = function (node) {
return t.isUserWhitespacable(node);
};
Node.prototype.needsWhitespace = function (type) {
var parent = this.parent;
var node = this.node;
Node.needsWhitespace = function (node, parent, type) {
if (!node) return 0;
if (t.isExpressionStatement(node)) {
@ -51,18 +49,15 @@ Node.prototype.needsWhitespace = function (type) {
return lines || 0;
};
Node.prototype.needsWhitespaceBefore = function () {
return this.needsWhitespace("before");
Node.needsWhitespaceBefore = function (node, parent) {
return Node.needsWhitespace(node, parent, "before");
};
Node.prototype.needsWhitespaceAfter = function () {
return this.needsWhitespace("after");
Node.needsWhitespaceAfter = function (node, parent) {
return Node.needsWhitespace(node, parent, "after");
};
Node.prototype.needsParens = function () {
var parent = this.parent;
var node = this.node;
Node.needsParens = function (node, parent) {
if (!parent) return false;
if (t.isNewExpression(parent) && parent.callee === node) {
@ -77,10 +72,7 @@ Node.prototype.needsParens = function () {
return find(parens, node, parent);
};
Node.prototype.needsParensNoLineTerminator = function () {
var parent = this.parent;
var node = this.node;
Node.needsParensNoLineTerminator = function (node, parent) {
if (!parent) return false;
// no comments
@ -100,17 +92,18 @@ Node.prototype.needsParensNoLineTerminator = function () {
return false;
};
_.each(Node.prototype, function (fn, key) {
Node[key] = function (node, parent) {
var n = new Node(node, parent);
_.each(Node, function (fn, key) {
Node.prototype[key] = function () {
// Avoid leaking arguments to prevent deoptimization
var skipCount = 2;
var args = new Array(arguments.length - skipCount);
var args = new Array(arguments.length + 2);
args[0] = this.node;
args[1] = this.parent;
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

@ -1,10 +1,38 @@
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) {
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) {
@ -13,13 +41,17 @@ Whitespace.prototype.getNewlinesBefore = function (node) {
var tokens = this.tokens;
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];
// this is the token this node starts with
if (node.start === token.start) {
startToken = tokens[i - 1];
endToken = token;
this._lastFoundIndex = i;
break;
}
}
@ -33,13 +65,17 @@ Whitespace.prototype.getNewlinesAfter = function (node) {
var tokens = this.tokens;
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];
// this is the token this node ends with
if (node.end === token.end) {
startToken = token;
endToken = tokens[i + 1];
this._lastFoundIndex = i;
break;
}
}
@ -60,15 +96,14 @@ Whitespace.prototype.getNewlinesAfter = function (node) {
Whitespace.prototype.getNewlinesBetween = function (startToken, endToken) {
var start = startToken ? startToken.loc.end.line : 1;
var end = endToken.loc.start.line;
var lines = 0;
for (var line = start; line < end; line++) {
if (!_.contains(this.used, line)) {
this.used.push(line);
if (typeof this.used[line] === 'undefined') {
this.used[line] = true;
lines++;
}
}
return 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) {
opts = opts || {};
if (!is(node, opts)) {
throw new Error("Expected type " + JSON.stringify(type) + " with option " + JSON.stringify(opts));
}
};
};
}
t.STATEMENT_OR_BLOCK_KEYS = ["consequent", "body"];
@ -22,14 +33,59 @@ t.STATEMENT_OR_BLOCK_KEYS = ["consequent", "body"];
t.VISITOR_KEYS = require("./visitor-keys");
_.each(t.VISITOR_KEYS, function (keys, type) {
var is = t["is" + type] = function (node, opts) {
return node && node.type === type && t.shallowEqual(node, opts);
};
t.ALIAS_KEYS = require("./alias-keys");
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);
@ -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
*
@ -139,17 +172,17 @@ t.toSequenceExpression = function (nodes, scope) {
//
t.shallowEqual = function (actual, expected) {
var same = true;
var keys = Object.keys(expected);
var key;
if (expected) {
_.each(expected, function (val, key) {
if (actual[key] !== val) {
return same = false;
}
});
for (var i = 0; i < keys.length; i++) {
key = keys[i];
if (actual[key] !== expected[key])
return false;
}
return same;
return true;
};
/**