improve Dictionary performance

This commit is contained in:
alexlamsl 2021-12-05 02:44:18 +08:00
parent 860aa9531b
commit e4ba7fad2a
3 changed files with 123 additions and 116 deletions

View File

@ -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) {

View File

@ -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;
}; };

View File

@ -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',