fix badfilter option; performance work

- badfilter option was no longer working following last refactoring
  changes.
- performance work:
    - reduce duplication of large strings.
    - new lighter FilterBucket to use when only 2 filters: FilterPair.
This commit is contained in:
gorhill 2017-05-26 20:00:21 -04:00
parent 1c685c86a7
commit aae97b8535
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
3 changed files with 176 additions and 17 deletions

View File

@ -121,8 +121,8 @@ var µBlock = (function() { // jshint ignore:line
// read-only // read-only
systemSettings: { systemSettings: {
compiledMagic: 'ohszqbtqggmp', compiledMagic: 'pwvcdyqfkuek',
selfieMagic: 'ohszqbtqggmp' selfieMagic: 'pwvcdyqfkuek'
}, },
restoreBackupSettings: { restoreBackupSettings: {

View File

@ -259,6 +259,23 @@ var isHnAnchored = function(url, matchStart) {
var reURLPostHostnameAnchors = /[\/?#]/; var reURLPostHostnameAnchors = /[\/?#]/;
var arrayStrictEquals = function(a, b) {
var n = a.length;
if ( n !== b.length ) { return false; }
var isArray, x, y;
for ( var i = 0; i < n; i++ ) {
x = a[i]; y = b[i];
isArray = Array.isArray(x);
if ( isArray !== Array.isArray(y) ) { return false; }
if ( isArray === true ) {
if ( arrayStrictEquals(x, y) === false ) { return false; }
} else {
if ( x !== y ) { return false; }
}
}
return true;
};
/******************************************************************************* /*******************************************************************************
Each filter class will register itself in the map. A filter class Each filter class will register itself in the map. A filter class
@ -804,7 +821,9 @@ FilterOriginMiss.prototype = Object.create(FilterOrigin.prototype, {
var FilterOriginHitSet = function(domainOpt) { var FilterOriginHitSet = function(domainOpt) {
FilterOrigin.call(this); FilterOrigin.call(this);
this.domainOpt = domainOpt; this.domainOpt = domainOpt.length < 128
? domainOpt
: µb.stringDeduplicater.lookup(domainOpt);
}; };
FilterOriginHitSet.prototype = Object.create(FilterOrigin.prototype, { FilterOriginHitSet.prototype = Object.create(FilterOrigin.prototype, {
@ -834,7 +853,9 @@ FilterOriginHitSet.prototype = Object.create(FilterOrigin.prototype, {
var FilterOriginMissSet = function(domainOpt) { var FilterOriginMissSet = function(domainOpt) {
FilterOrigin.call(this); FilterOrigin.call(this);
this.domainOpt = domainOpt; this.domainOpt = domainOpt.length < 128
? domainOpt
: µb.stringDeduplicater.lookup(domainOpt);
}; };
FilterOriginMissSet.prototype = Object.create(FilterOrigin.prototype, { FilterOriginMissSet.prototype = Object.create(FilterOrigin.prototype, {
@ -864,7 +885,9 @@ FilterOriginMissSet.prototype = Object.create(FilterOrigin.prototype, {
var FilterOriginMixedSet = function(domainOpt) { var FilterOriginMixedSet = function(domainOpt) {
FilterOrigin.call(this); FilterOrigin.call(this);
this.domainOpt = domainOpt; this.domainOpt = domainOpt.length < 128
? domainOpt
: µb.stringDeduplicater.lookup(domainOpt);
}; };
FilterOriginMixedSet.prototype = Object.create(FilterOrigin.prototype, { FilterOriginMixedSet.prototype = Object.create(FilterOrigin.prototype, {
@ -1115,15 +1138,91 @@ registerFilterClass(FilterHostnameDict);
/******************************************************************************/ /******************************************************************************/
var FilterBucket = function(a, b) { var FilterPair = function(a, b) {
this.f1 = a;
this.f2 = b;
this.f = null;
};
Object.defineProperty(FilterPair.prototype, 'size', {
get: function() {
if ( this.f1 === undefined && this.f2 === undefined ) { return 0; }
if ( this.f1 === undefined || this.f2 === undefined ) { return 1; }
return 2;
}
});
FilterPair.prototype.remove = function(fdata) {
if ( arrayStrictEquals(this.f2.compile(), fdata) === true ) {
this.f2 = undefined;
}
if ( arrayStrictEquals(this.f1.compile(), fdata) === true ) {
this.f1 = this.f2;
}
};
FilterPair.prototype.match = function(url, tokenBeg) {
if ( this.f1.match(url, tokenBeg) === true ) {
this.f = this.f1;
return true;
}
if ( this.f2.match(url, tokenBeg) === true ) {
this.f = this.f2;
return true;
}
return false;
};
FilterPair.prototype.logData = function() {
return this.f.logData();
};
FilterPair.prototype.compile = function() {
return [ this.fid, this.f1.compile(), this.f2.compile() ];
};
FilterPair.prototype.upgrade = function(a) {
var bucket = new FilterBucket(this.f1, this.f2, a);
this.f1 = this.f2 = this.f = null;
FilterPair.available = this;
return bucket;
};
FilterPair.load = function(args) {
var f1 = filterFromCompiledData(args[1]),
f2 = filterFromCompiledData(args[2]),
pair = FilterPair.available;
if ( pair === null ) {
return new FilterPair(f1, f2);
}
FilterPair.available = null;
pair.f1 = f1;
pair.f2 = f2;
return pair;
};
FilterPair.available = null;
registerFilterClass(FilterPair);
/******************************************************************************/
var FilterBucket = function(a, b, c) {
this.filters = []; this.filters = [];
this.f = null; this.f = null;
if ( a !== undefined ) { if ( a !== undefined ) {
this.filters[0] = a; this.filters[0] = a;
this.filters[1] = b; this.filters[1] = b;
this.filters[2] = c;
} }
}; };
Object.defineProperty(FilterBucket.prototype, 'size', {
get: function() {
return this.filters.length;
}
});
FilterBucket.prototype.promoted = 0; FilterBucket.prototype.promoted = 0;
FilterBucket.prototype.add = function(fdata) { FilterBucket.prototype.add = function(fdata) {
@ -1135,7 +1234,7 @@ FilterBucket.prototype.remove = function(fdata) {
filter; filter;
while ( i-- ) { while ( i-- ) {
filter = this.filters[i]; filter = this.filters[i];
if ( filter.compile() === fdata ) { if ( arrayStrictEquals(filter.compile(), fdata) === true ) {
this.filters.splice(i, 1); this.filters.splice(i, 1);
} }
} }
@ -1184,13 +1283,13 @@ FilterBucket.prototype.compile = function() {
}; };
FilterBucket.load = function(args) { FilterBucket.load = function(args) {
var f = new FilterBucket(), var bucket = new FilterBucket(),
compiledFilters = args[1], compiledFilters = args[1],
filters = f.filters; filters = bucket.filters;
for ( var i = 0, n = compiledFilters.length; i < n; i++ ) { for ( var i = 0, n = compiledFilters.length; i < n; i++ ) {
filters[i] = filterFromCompiledData(compiledFilters[i]); filters[i] = filterFromCompiledData(compiledFilters[i]);
} }
return f; return bucket;
}; };
registerFilterClass(FilterBucket); registerFilterClass(FilterBucket);
@ -2063,6 +2162,7 @@ FilterContainer.prototype.compileToAtomicFilter = function(fdata, parsed, writer
FilterContainer.prototype.fromCompiledContent = function(reader) { FilterContainer.prototype.fromCompiledContent = function(reader) {
var badFilterBit = BadFilter, var badFilterBit = BadFilter,
filterPairId = FilterPair.fid,
filterBucketId = FilterBucket.fid, filterBucketId = FilterBucket.fid,
filterDataHolderId = FilterDataHolder.fid, filterDataHolderId = FilterDataHolder.fid,
args, bits, bucket, entry, args, bits, bucket, entry,
@ -2141,14 +2241,27 @@ FilterContainer.prototype.fromCompiledContent = function(reader) {
entry.add(fdata); entry.add(fdata);
continue; continue;
} }
bucket.set(tokenHash, new FilterBucket(entry, filterFromCompiledData(fdata))); if ( entry.fid === filterPairId ) {
bucket.set(
tokenHash,
entry.upgrade(filterFromCompiledData(fdata))
);
continue;
}
bucket.set(
tokenHash,
new FilterPair(entry, filterFromCompiledData(fdata))
);
} }
}; };
/******************************************************************************/ /******************************************************************************/
FilterContainer.prototype.removeBadFilters = function() { FilterContainer.prototype.removeBadFilters = function() {
var bits, tokenHash, fdata, bucket, entry; var filterPairId = FilterPair.fid,
filterBucketId = FilterBucket.fid,
filterHostnameDictId = FilterHostnameDict.fid,
bits, tokenHash, fdata, bucket, entry;
for ( var args of this.badFilters ) { for ( var args of this.badFilters ) {
bits = args[0] & ~BadFilter; bits = args[0] & ~BadFilter;
bucket = this.categories.get(bits); bucket = this.categories.get(bits);
@ -2157,14 +2270,24 @@ FilterContainer.prototype.removeBadFilters = function() {
entry = bucket.get(tokenHash); entry = bucket.get(tokenHash);
if ( entry === undefined ) { continue; } if ( entry === undefined ) { continue; }
fdata = args[2]; fdata = args[2];
if ( entry instanceof FilterBucket ) { if ( entry.fid === filterPairId ) {
entry.remove(fdata); entry.remove(fdata);
if ( entry.filters.length === 1 ) { if ( entry.size === 1 ) {
bucket.set(tokenHash, entry.filters[0]); bucket.set(tokenHash, entry.f1);
} }
continue; continue;
} }
if ( entry instanceof FilterHostnameDict ) { if ( entry.fid === filterBucketId ) {
entry.remove(fdata);
if ( entry.size === 2 ) {
bucket.set(
tokenHash,
new FilterPair(entry.filters[0], entry.filters[1])
);
}
continue;
}
if ( entry.fid === filterHostnameDictId ) {
entry.remove(fdata); entry.remove(fdata);
if ( entry.size === 0 ) { if ( entry.size === 0 ) {
bucket.delete(tokenHash); bucket.delete(tokenHash);
@ -2174,7 +2297,7 @@ FilterContainer.prototype.removeBadFilters = function() {
} }
continue; continue;
} }
if ( entry.compile() === fdata ) { if ( arrayStrictEquals(entry.compile(), fdata) === true ) {
bucket.delete(tokenHash); bucket.delete(tokenHash);
if ( bucket.size === 0 ) { if ( bucket.size === 0 ) {
this.categories.delete(bits); this.categories.delete(bits);

View File

@ -280,6 +280,42 @@
/******************************************************************************/ /******************************************************************************/
// I want this helper to be self-maintained, callers must not worry about
// this helper cleaning after itself by asking them to reset it when it is no
// longer needed. A timer will be used for self-garbage-collect.
// Cleaning up 10s after last hit sounds reasonable.
µBlock.stringDeduplicater = {
strings: new Map(),
timer: undefined,
last: 0,
lookup: function(s) {
var t = this.strings.get(s);
if ( t === undefined ) {
t = this.strings.set(s, s).get(s);
if ( this.timer === undefined ) { this.cleanupAsync(); }
}
this.last = Date.now();
return t;
},
cleanupAsync: function() {
this.timer = vAPI.setTimeout(this.cleanup.bind(this), 10000);
},
cleanup: function() {
if ( (Date.now() - this.last) < 10000 ) {
this.timer = vAPI.setTimeout(this.cleanup.bind(this), 10000);
} else {
this.timer = undefined;
this.strings.clear();
}
}
};
/******************************************************************************/
µBlock.mapToArray = typeof Array.from === 'function' µBlock.mapToArray = typeof Array.from === 'function'
? Array.from ? Array.from
: function(map) { : function(map) {