improve Dictionary performance
This commit is contained in:
parent
860aa9531b
commit
e4ba7fad2a
161
lib/compress.js
161
lib/compress.js
|
|
@ -185,90 +185,89 @@ function Compressor(options, false_by_default) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Compressor.prototype = new TreeTransformer;
|
Compressor.prototype = new TreeTransformer(function(node, descend, in_list) {
|
||||||
merge(Compressor.prototype, {
|
if (node._squeezed) return node;
|
||||||
option: function(key) { return this.options[key] },
|
var is_scope = node instanceof AST_Scope;
|
||||||
exposed: function(def) {
|
if (is_scope) {
|
||||||
if (def.exported) return true;
|
node.hoist_properties(this);
|
||||||
if (def.undeclared) return true;
|
node.hoist_declarations(this);
|
||||||
if (!(def.global || def.scope.resolve() instanceof AST_Toplevel)) return false;
|
node.process_boolean_returns(this);
|
||||||
var toplevel = this.toplevel;
|
}
|
||||||
return !all(def.orig, function(sym) {
|
// Before https://github.com/mishoo/UglifyJS/pull/1602 AST_Node.optimize()
|
||||||
return toplevel[sym instanceof AST_SymbolDefun ? "funcs" : "vars"];
|
// would call AST_Node.transform() if a different instance of AST_Node is
|
||||||
});
|
// produced after OPT().
|
||||||
},
|
// This corrupts TreeWalker.stack, which cause AST look-ups to malfunction.
|
||||||
compress: function(node) {
|
// Migrate and defer all children's AST_Node.transform() to below, which
|
||||||
node = node.resolve_defines(this);
|
// will now happen after this parent AST_Node has been properly substituted
|
||||||
node.hoist_exports(this);
|
// thus gives a consistent AST snapshot.
|
||||||
if (this.option("expression")) {
|
descend(node, this);
|
||||||
node.process_expression(true);
|
// Existing code relies on how AST_Node.optimize() worked, and omitting the
|
||||||
}
|
// following replacement call would result in degraded efficiency of both
|
||||||
var merge_vars = this.options.merge_vars;
|
// output and performance.
|
||||||
var passes = +this.options.passes || 1;
|
descend(node, this);
|
||||||
var min_count = 1 / 0;
|
var opt = node.optimize(this);
|
||||||
var stopping = false;
|
if (is_scope && opt === node && !this.has_directive("use asm") && !opt.pinned()) {
|
||||||
var mangle = { ie: this.option("ie") };
|
opt.drop_unused(this);
|
||||||
for (var pass = 0; pass < passes; pass++) {
|
if (opt.merge_variables(this)) opt.drop_unused(this);
|
||||||
node.figure_out_scope(mangle);
|
descend(opt, this);
|
||||||
if (pass > 0 || this.option("reduce_vars"))
|
}
|
||||||
node.reset_opt_flags(this);
|
if (opt === node) opt._squeezed = true;
|
||||||
this.options.merge_vars = merge_vars && (stopping || pass == passes - 1);
|
return opt;
|
||||||
node = node.transform(this);
|
});
|
||||||
if (passes > 1) {
|
Compressor.prototype.option = function(key) {
|
||||||
var count = 0;
|
return this.options[key];
|
||||||
node.walk(new TreeWalker(function() {
|
};
|
||||||
count++;
|
Compressor.prototype.exposed = function(def) {
|
||||||
}));
|
if (def.exported) return true;
|
||||||
AST_Node.info("pass {pass}: last_count: {min_count}, count: {count}", {
|
if (def.undeclared) return true;
|
||||||
pass: pass,
|
if (!(def.global || def.scope.resolve() instanceof AST_Toplevel)) return false;
|
||||||
min_count: min_count,
|
var toplevel = this.toplevel;
|
||||||
count: count,
|
return !all(def.orig, function(sym) {
|
||||||
});
|
return toplevel[sym instanceof AST_SymbolDefun ? "funcs" : "vars"];
|
||||||
if (count < min_count) {
|
});
|
||||||
min_count = count;
|
};
|
||||||
stopping = false;
|
Compressor.prototype.compress = function(node) {
|
||||||
} else if (stopping) {
|
node = node.resolve_defines(this);
|
||||||
break;
|
node.hoist_exports(this);
|
||||||
} else {
|
if (this.option("expression")) {
|
||||||
stopping = true;
|
node.process_expression(true);
|
||||||
}
|
}
|
||||||
|
var merge_vars = this.options.merge_vars;
|
||||||
|
var passes = +this.options.passes || 1;
|
||||||
|
var min_count = 1 / 0;
|
||||||
|
var stopping = false;
|
||||||
|
var mangle = { ie: this.option("ie") };
|
||||||
|
for (var pass = 0; pass < passes; pass++) {
|
||||||
|
node.figure_out_scope(mangle);
|
||||||
|
if (pass > 0 || this.option("reduce_vars"))
|
||||||
|
node.reset_opt_flags(this);
|
||||||
|
this.options.merge_vars = merge_vars && (stopping || pass == passes - 1);
|
||||||
|
node = node.transform(this);
|
||||||
|
if (passes > 1) {
|
||||||
|
var count = 0;
|
||||||
|
node.walk(new TreeWalker(function() {
|
||||||
|
count++;
|
||||||
|
}));
|
||||||
|
AST_Node.info("pass {pass}: last_count: {min_count}, count: {count}", {
|
||||||
|
pass: pass,
|
||||||
|
min_count: min_count,
|
||||||
|
count: count,
|
||||||
|
});
|
||||||
|
if (count < min_count) {
|
||||||
|
min_count = count;
|
||||||
|
stopping = false;
|
||||||
|
} else if (stopping) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
stopping = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.option("expression")) {
|
|
||||||
node.process_expression(false);
|
|
||||||
}
|
|
||||||
return node;
|
|
||||||
},
|
|
||||||
before: function(node, descend, in_list) {
|
|
||||||
if (node._squeezed) return node;
|
|
||||||
var is_scope = node instanceof AST_Scope;
|
|
||||||
if (is_scope) {
|
|
||||||
node.hoist_properties(this);
|
|
||||||
node.hoist_declarations(this);
|
|
||||||
node.process_boolean_returns(this);
|
|
||||||
}
|
|
||||||
// Before https://github.com/mishoo/UglifyJS/pull/1602 AST_Node.optimize()
|
|
||||||
// would call AST_Node.transform() if a different instance of AST_Node is
|
|
||||||
// produced after OPT().
|
|
||||||
// This corrupts TreeWalker.stack, which cause AST look-ups to malfunction.
|
|
||||||
// Migrate and defer all children's AST_Node.transform() to below, which
|
|
||||||
// will now happen after this parent AST_Node has been properly substituted
|
|
||||||
// thus gives a consistent AST snapshot.
|
|
||||||
descend(node, this);
|
|
||||||
// Existing code relies on how AST_Node.optimize() worked, and omitting the
|
|
||||||
// following replacement call would result in degraded efficiency of both
|
|
||||||
// output and performance.
|
|
||||||
descend(node, this);
|
|
||||||
var opt = node.optimize(this);
|
|
||||||
if (is_scope && opt === node && !this.has_directive("use asm") && !opt.pinned()) {
|
|
||||||
opt.drop_unused(this);
|
|
||||||
if (opt.merge_variables(this)) opt.drop_unused(this);
|
|
||||||
descend(opt, this);
|
|
||||||
}
|
|
||||||
if (opt === node) opt._squeezed = true;
|
|
||||||
return opt;
|
|
||||||
}
|
}
|
||||||
});
|
if (this.option("expression")) {
|
||||||
|
node.process_expression(false);
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
};
|
||||||
|
|
||||||
(function(OPT) {
|
(function(OPT) {
|
||||||
OPT(AST_Node, function(self, compressor) {
|
OPT(AST_Node, function(self, compressor) {
|
||||||
|
|
|
||||||
74
lib/utils.js
74
lib/utils.js
|
|
@ -96,15 +96,6 @@ function defaults(args, defs, croak) {
|
||||||
return defs;
|
return defs;
|
||||||
}
|
}
|
||||||
|
|
||||||
function merge(obj, ext) {
|
|
||||||
var count = 0;
|
|
||||||
for (var i in ext) if (HOP(ext, i)) {
|
|
||||||
obj[i] = ext[i];
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
function noop() {}
|
function noop() {}
|
||||||
function return_false() { return false; }
|
function return_false() { return false; }
|
||||||
function return_true() { return true; }
|
function return_true() { return true; }
|
||||||
|
|
@ -171,63 +162,80 @@ function all(array, predicate) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function Dictionary() {
|
function Dictionary() {
|
||||||
this._values = Object.create(null);
|
this.values = Object.create(null);
|
||||||
this._size = 0;
|
|
||||||
}
|
}
|
||||||
Dictionary.prototype = {
|
Dictionary.prototype = {
|
||||||
set: function(key, val) {
|
set: function(key, val) {
|
||||||
if (!this.has(key)) ++this._size;
|
if (key == "__proto__") {
|
||||||
this._values["$" + key] = val;
|
this.proto_value = val;
|
||||||
|
} else {
|
||||||
|
this.values[key] = val;
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
add: function(key, val) {
|
add: function(key, val) {
|
||||||
if (this.has(key)) {
|
var list = this.get(key);
|
||||||
this.get(key).push(val);
|
if (list) {
|
||||||
|
list.push(val);
|
||||||
} else {
|
} else {
|
||||||
this.set(key, [ val ]);
|
this.set(key, [ val ]);
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
get: function(key) { return this._values["$" + key] },
|
get: function(key) {
|
||||||
|
return key == "__proto__" ? this.proto_value : this.values[key];
|
||||||
|
},
|
||||||
del: function(key) {
|
del: function(key) {
|
||||||
if (this.has(key)) {
|
if (key == "__proto__") {
|
||||||
--this._size;
|
delete this.proto_value;
|
||||||
delete this._values["$" + key];
|
} else {
|
||||||
|
delete this.values[key];
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
has: function(key) { return ("$" + key) in this._values },
|
has: function(key) {
|
||||||
|
return key == "__proto__" ? "proto_value" in this : key in this.values;
|
||||||
|
},
|
||||||
all: function(predicate) {
|
all: function(predicate) {
|
||||||
for (var i in this._values)
|
for (var i in this.values)
|
||||||
if (!predicate(this._values[i], i.substr(1)))
|
if (!predicate(this.values[i], i)) return false;
|
||||||
return false;
|
if ("proto_value" in this && !predicate(this.proto_value, "__proto__")) return false;
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
each: function(f) {
|
each: function(f) {
|
||||||
for (var i in this._values)
|
for (var i in this.values)
|
||||||
f(this._values[i], i.substr(1));
|
f(this.values[i], i);
|
||||||
|
if ("proto_value" in this) f(this.proto_value, "__proto__");
|
||||||
},
|
},
|
||||||
size: function() {
|
size: function() {
|
||||||
return this._size;
|
return Object.keys(this.values).length + ("proto_value" in this);
|
||||||
},
|
},
|
||||||
map: function(f) {
|
map: function(f) {
|
||||||
var ret = [];
|
var ret = [];
|
||||||
for (var i in this._values)
|
for (var i in this.values)
|
||||||
ret.push(f(this._values[i], i.substr(1)));
|
ret.push(f(this.values[i], i));
|
||||||
|
if ("proto_value" in this) ret.push(f(this.proto_value, "__proto__"));
|
||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
clone: function() {
|
clone: function() {
|
||||||
var ret = new Dictionary();
|
var ret = new Dictionary();
|
||||||
for (var i in this._values)
|
this.each(function(value, i) {
|
||||||
ret._values[i] = this._values[i];
|
ret.set(i, value);
|
||||||
ret._size = this._size;
|
});
|
||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
toObject: function() { return this._values }
|
toObject: function() {
|
||||||
|
var obj = {};
|
||||||
|
this.each(function(value, i) {
|
||||||
|
obj["$" + i] = value;
|
||||||
|
});
|
||||||
|
return obj;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
Dictionary.fromObject = function(obj) {
|
Dictionary.fromObject = function(obj) {
|
||||||
var dict = new Dictionary();
|
var dict = new Dictionary();
|
||||||
dict._size = merge(dict._values, obj);
|
for (var i in obj)
|
||||||
|
if (HOP(obj, i)) dict.set(i.slice(1), obj[i]);
|
||||||
return dict;
|
return dict;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -198,9 +198,9 @@ numeric_literal: {
|
||||||
expect_exact: [
|
expect_exact: [
|
||||||
'var obj = {',
|
'var obj = {',
|
||||||
' 0: 0,',
|
' 0: 0,',
|
||||||
' "-0": 1,',
|
|
||||||
' 42: 3,',
|
|
||||||
' 37: 4,',
|
' 37: 4,',
|
||||||
|
' 42: 3,',
|
||||||
|
' "-0": 1,',
|
||||||
' o: 5,',
|
' o: 5,',
|
||||||
' 1e42: 8,',
|
' 1e42: 8,',
|
||||||
' b: 7',
|
' b: 7',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user