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,10 +185,39 @@ 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) {
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;
});
Compressor.prototype.option = function(key) {
return this.options[key];
};
Compressor.prototype.exposed = function(def) {
if (def.exported) return true; if (def.exported) return true;
if (def.undeclared) return true; if (def.undeclared) return true;
if (!(def.global || def.scope.resolve() instanceof AST_Toplevel)) return false; if (!(def.global || def.scope.resolve() instanceof AST_Toplevel)) return false;
@ -196,8 +225,8 @@ merge(Compressor.prototype, {
return !all(def.orig, function(sym) { return !all(def.orig, function(sym) {
return toplevel[sym instanceof AST_SymbolDefun ? "funcs" : "vars"]; return toplevel[sym instanceof AST_SymbolDefun ? "funcs" : "vars"];
}); });
}, };
compress: function(node) { Compressor.prototype.compress = function(node) {
node = node.resolve_defines(this); node = node.resolve_defines(this);
node.hoist_exports(this); node.hoist_exports(this);
if (this.option("expression")) { if (this.option("expression")) {
@ -238,37 +267,7 @@ merge(Compressor.prototype, {
node.process_expression(false); node.process_expression(false);
} }
return node; 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;
}
});
(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',