From 3223e9733a96ce5c4e1140924f91ee61604acad2 Mon Sep 17 00:00:00 2001 From: Zaid Abdulla Date: Wed, 15 Feb 2017 02:19:02 -0800 Subject: [PATCH] Optimize PathSet for V8 PathSet.find has been rewritten without using ES6 generators in order to avoid V8's TurboFan/Crankshift optimization bailout. --- lib/cwmp.coffee | 3 +- lib/db.coffee | 2 +- lib/device.js | 10 +-- lib/path-set.js | 194 +++++++++++++++++++++------------------------ lib/session.coffee | 44 ++++------ 5 files changed, 115 insertions(+), 138 deletions(-) diff --git a/lib/cwmp.coffee b/lib/cwmp.coffee index 4d4d32b..55314bb 100644 --- a/lib/cwmp.coffee +++ b/lib/cwmp.coffee @@ -244,8 +244,7 @@ applyPresets = (currentRequest) -> sessionData.presetsHash = presetsHash deviceEvents = {} - iter = sessionData.deviceData.paths.subset(['Events', '*']) - while (p = iter.next().value) + for p in sessionData.deviceData.paths.find(['Events', '*'], false, true) if sessionData.timestamp <= sessionData.deviceData.attributes.get(p)?.value?[1][0] deviceEvents[p[1]] = true diff --git a/lib/db.coffee b/lib/db.coffee index c84a6ca..e06d25a 100644 --- a/lib/db.coffee +++ b/lib/db.coffee @@ -339,7 +339,7 @@ saveDevice = (deviceId, deviceData, isNew, callback) -> if not diff[2]? and diff[1]? update['$unset'][diff[0].slice(0, -1).concat('_timestamp').join('.')] = 1 else if diff[2] != diff[1] - parent = deviceData.paths.subset(diff[0].slice(0, -1)).next().value + parent = deviceData.paths.get(diff[0].slice(0, -1)) if parent and (parent.length == 0 or deviceData.attributes.has(parent)) update['$set'][diff[0].slice(0, -1).concat('_timestamp').join('.')] = new Date(diff[2]) diff --git a/lib/device.js b/lib/device.js index a30fac5..65c0156 100644 --- a/lib/device.js +++ b/lib/device.js @@ -96,7 +96,7 @@ function getAliasDeclarations(path, timestamp) { function unpack(deviceData, path, revision) { let allMatches = []; if (!path.alias) { - for (let p of deviceData.paths.subset(path, 0)) + for (let p of deviceData.paths.find(path, false, true)) if (deviceData.attributes.has(p, revision)) allMatches.push(p); } @@ -109,7 +109,7 @@ function unpack(deviceData, path, revision) { wildcardPath.wildcard |= 1 << i; } - for (let p of deviceData.paths.subset(wildcardPath, 0)) + for (let p of deviceData.paths.find(wildcardPath, false, true)) if (deviceData.attributes.has(p, revision)) allMatches.push(p); @@ -169,7 +169,7 @@ function unpack(deviceData, path, revision) { function clear(deviceData, path, timestamp, attributes) { - for (let p of deviceData.paths.all(path, timestamp ? 99 : 0)) { + for (let p of deviceData.paths.find(path, true, true, timestamp ? 99 : path.length)) { for (let k in deviceData.trackers.get(p)) deviceData.changes.add(k); @@ -279,7 +279,7 @@ function set(deviceData, path, timestamp, attributes, toClear) { } if (changeFlags) - for (let sup of deviceData.paths.superset(path)) { + for (let sup of deviceData.paths.find(path, true, false)) { let c = deviceData.trackers.get(sup); for (let k in c) if (c[k] & changeFlags) { @@ -309,7 +309,7 @@ function set(deviceData, path, timestamp, attributes, toClear) { } if (path.wildcard) - for (let sub of deviceData.paths.subset(path)) + for (let sub of deviceData.paths.find(path, false, true)) if (timestamp > deviceData.timestamps.get(sub)) { deviceData.timestamps.delete(sub); if (deviceData.attributes.delete(sub)) { diff --git a/lib/path-set.js b/lib/path-set.js index 4a9c964..b1c478c 100644 --- a/lib/path-set.js +++ b/lib/path-set.js @@ -31,60 +31,28 @@ class PathSet { add(path) { - let sets; + let p = this.get(path); + + if (p) + return p; if (!this.lengthIndex[path.length]) this.lengthIndex[path.length] = new Set(); - else if (this.lengthIndex[path.length].has(path)) - return path; - else - sets = [this.lengthIndex[path.length]]; - for (let i = 0; i < path.length; ++ i) { - let fragment = path[i]; - - if (!fragment) - throw new Error('All array elements must be non-empty string'); - - if (!this.fragmentIndex[i]) { - sets = null; - this.fragmentIndex[i] = {} - } - - if (!this.fragmentIndex[i][fragment]) { - sets = null; - this.fragmentIndex[i][fragment] = new Set(); - } - else if (sets) - sets.push(this.fragmentIndex[i][fragment]) - } - - if (sets) { - sets.sort(function(a, b) { - return a.size - b.size; - }); - - var smallest = sets.shift(); - for (let p of smallest) { - for (let set of sets) { - if (!set.has(p)) { - p = null; - break; - } - } - - if (p) - return p; - } - } - - var wildcard = 0; this.lengthIndex[path.length].add(path); + + let wildcard = 0; for (let i = 0; i < path.length; ++ i) { let fragment = path[i]; - if (fragment === '*') + if (fragment === "*") wildcard |= 1 << i; + if (!this.fragmentIndex[i]) + this.fragmentIndex[i] = {}; + + if (!this.fragmentIndex[i][fragment]) + this.fragmentIndex[i][fragment] = new Set(); + this.fragmentIndex[i][fragment].add(path); } @@ -97,90 +65,110 @@ class PathSet { } - *subset(path, depth) { - var p = path.slice(); - for (let i = 0; i < p.length; ++ i) - if (p[i] === '*') - p[i] = null; - yield* this.find(p, depth || 0, false); - } + get(pattern) { + if (!this.lengthIndex[pattern.length]) + return null; + if (this.lengthIndex[pattern.length].has(pattern)) + return pattern; - *superset(path, depth) { - yield* this.find(path, depth || 0, true) - } + let sets = []; - - *all(path, depth) { - var p = path.slice(); - for (let i = 0; i < p.length; ++ i) - if (p[i] === '*') - p[i] = null; - yield* this.find(p, depth || 0, true); - } - - - *find(pattern, depth, wildcard) { - var sets = []; - - var s = []; - for (let i = pattern.length + depth; i >= pattern.length; -- i) { - if (this.lengthIndex[i]) - s.push(this.lengthIndex[i]); - } - - if (!s.length) - return; - - sets.push(s); + sets.push(this.lengthIndex[pattern.length]); for (let i = 0; i < pattern.length; ++ i) { - if (pattern[i]) { - let fragment = pattern[i]; - let s = []; + if (!this.fragmentIndex[i]) + return null; - if (this.fragmentIndex[i]) { - if (this.fragmentIndex[i][fragment]) - s.push(this.fragmentIndex[i][fragment]); + let fragment = pattern[i]; - if (wildcard && fragment != '*' && this.fragmentIndex[i]['*']) - s.push(this.fragmentIndex[i]['*']) - } + if (!this.fragmentIndex[i][fragment]) + return null; - if (!s.length) - return; - - sets.push(s); - } + sets.push(this.fragmentIndex[i][fragment]); } sets.sort(function(a, b) { + return a.size - b.size; + }); + + let smallestSet = sets.shift(); + for (let path of smallestSet) { + for (let s of sets) { + if (!s.has(path)) { + path = null; + break + } + } + if (path) + return path; + } + + return null; + } + + + find(pattern, superset = false, subset = false, depth = pattern.length) { + let res = []; + let groups = [[]]; + + for (let i = pattern.length; i <= depth; ++ i) + if (this.lengthIndex[i]) + groups[0].push(this.lengthIndex[i]); + + if (!groups[0].length) + return res; + + for (let i = 0; i < pattern.length; ++ i) { + if (!this.fragmentIndex[i]) + return res; + + let fragment = pattern[i]; + + if (fragment === "*" && subset) + continue; + + let g = []; + + if (this.fragmentIndex[i][fragment]) + g.push(this.fragmentIndex[i][fragment]); + + if (fragment !== "*" && superset && this.fragmentIndex[i]["*"]) + g.push(this.fragmentIndex[i]["*"]); + + if (!g.length) + return res; + + groups.push(g); + } + + groups.sort(function(a, b) { return a.reduce(function(prev, cur) {return prev + cur.size;}, 0) - b.reduce(function(prev, cur) {return prev + cur.size;}, 0); }); - var smallest = sets.shift(); - for (let pp of smallest) { - for (let p of pp) { - for (let ss of sets) { + let smallestGroup = groups.shift(); + for (let set of smallestGroup) { + for (let path of set) { + for (let group of groups) { let found = false; - for (let s of ss) { - if (s.has(p)) { + for (let s of group) { + if (s.has(path)) { found = true; break; } } - if (!found) { - p = null; + path = null; break; } } - - if (p) - yield p; + if (path) + res.push(path); } } + + return res; } } diff --git a/lib/session.coffee b/lib/session.coffee index 6769e50..807e2d6 100644 --- a/lib/session.coffee +++ b/lib/session.coffee @@ -671,8 +671,7 @@ rpcRequest = (sessionData, _declarations, callback) -> else if c? and not v toClear = device.set(sessionData.deviceData, p, timestamp, toClear) noMoreTags = true - iter = sessionData.deviceData.paths.subset(['Tags', '*']) - while p = iter.next().value + for p in sessionData.deviceData.paths.find(['Tags', '*'], false, true) if sessionData.deviceData.attributes.has(p) noMoreTags = false break @@ -685,8 +684,7 @@ rpcRequest = (sessionData, _declarations, callback) -> sessionData.syncState.downloadsToCreate.forEach((instance) -> if not index? index = 0 - iter = sessionData.deviceData.paths.subset(['Downloads', '*']) - while p = iter.next().value + for p in sessionData.deviceData.paths.find(['Downloads', '*'], false, true) if +p[1] > index and sessionData.deviceData.attributes.has(p) index = +p[1] @@ -831,9 +829,8 @@ generateGetRpcRequest = (sessionData) -> iter = syncState.refreshAttributes.exist.values() while (path = iter.next().value) - descendantIter = sessionData.deviceData.paths.subset(path, 99) found = false - while (p = descendantIter.next().value)? + for p in sessionData.deviceData.paths.find(path, false, true, 99) if syncState.refreshAttributes.value.has(p) or syncState.refreshAttributes.object.has(p) or syncState.refreshAttributes.writable.has(p) or @@ -849,9 +846,8 @@ generateGetRpcRequest = (sessionData) -> iter = syncState.refreshAttributes.object.values() while (path = iter.next().value) - descendantIter = sessionData.deviceData.paths.subset(path, 99) found = false - while (p = descendantIter.next().value)? + for p in sessionData.deviceData.paths.find(path, false, true, 99) if syncState.refreshAttributes.value.has(p) or (p.length > path.length and (syncState.refreshAttributes.object.has(p) or @@ -885,8 +881,7 @@ generateGetRpcRequest = (sessionData) -> if path patterns = [] - iter = sessionData.deviceData.paths.superset(path, 99) - while p = iter.next().value + for p in sessionData.deviceData.paths.find(path, true, false, 99) if v = syncState.gpnPatterns.get(p) patterns.push([p, (v >> path.length) << path.length]) @@ -900,8 +895,7 @@ generateGetRpcRequest = (sessionData) -> syncState.gpn.delete(path) else nextLevel = false - iter = sessionData.deviceData.paths.subset(path, 99) - while p = iter.next().value + for p in sessionData.deviceData.paths.find(path, false, true, 99) syncState.gpn.delete(p) return { @@ -985,7 +979,7 @@ generateSetRpcRequest = (sessionData) -> # Reboot if syncState.reboot? - p = sessionData.deviceData.paths.subset(['Reboot']).next().value + p = sessionData.deviceData.paths.get(['Reboot']) if not (p? and sessionData.deviceData.attributes.get(p)?.value?[1][0] >= syncState.reboot) delete syncState.reboot return { @@ -994,7 +988,7 @@ generateSetRpcRequest = (sessionData) -> # Factory reset if syncState.factoryReset? - p = sessionData.deviceData.paths.subset(['FactoryReset']).next().value + p = sessionData.deviceData.paths.get(['FactoryReset']) if not (p? and sessionData.deviceData.attributes.get(p)?.value?[1][0] >= syncState.factoryReset) delete syncState.factoryReset return { @@ -1005,9 +999,9 @@ generateSetRpcRequest = (sessionData) -> iter = syncState.downloadsDownload.entries() while pair = iter.next().value if not (pair[1] <= deviceData.attributes.get(pair[0])?.value?[1][0]) - fileTypePath = deviceData.paths.subset(pair[0].slice(0, -1).concat('FileType')).next().value - fileNamePath = deviceData.paths.subset(pair[0].slice(0, -1).concat('FileName')).next().value - targetFileNamePath = deviceData.paths.subset(pair[0].slice(0, -1).concat('TargetFileName')).next().value + fileTypePath = deviceData.paths.get(pair[0].slice(0, -1).concat('FileType')) + fileNamePath = deviceData.paths.get(pair[0].slice(0, -1).concat('FileName')) + targetFileNamePath = deviceData.paths.get(pair[0].slice(0, -1).concat('TargetFileName')) return { type: 'Download' commandKey: generateRpcId(sessionData) @@ -1054,7 +1048,7 @@ processDeclarations = (sessionData, allDeclareTimestamps, allDeclareAttributeTim syncState = sessionData.syncState root = sessionData.deviceData.paths.add([]) - paths = Array.from(sessionData.deviceData.paths.subset([], 99)) + paths = sessionData.deviceData.paths.find([], false, true, 99) paths.sort((a, b) -> if a.wildcard == b.wildcard return a.length - b.length @@ -1196,9 +1190,7 @@ loadPath = (sessionData, path, depth) -> path = path.slice(0, trimWildcard) if trimWildcard < path.length for i in [0..path.length] by 1 - iter = sessionData.deviceData.paths.superset(path.slice(0, i)) - - while sup = iter.next().value + for sup in sessionData.deviceData.paths.find(path.slice(0, i), true, false) v = sessionData.deviceData.loaded.get(sup) depth &= depth ^ sessionData.deviceData.loaded.get(sup) @@ -1259,7 +1251,7 @@ clear = (sessionData, toClear, callback) -> MAX_DEPTH = config.get('MAX_DEPTH', sessionData.deviceId) toClear.forEach((c) -> - loadPath(sessionData, c[0], ((1 << c[0].length) - 1) ^ (1 << MAX_DEPTH) - 1) + loadPath(sessionData, c[0], ((1 << c[0].length) - 1) ^ ((1 << MAX_DEPTH) - 1)) ) loadParameters(sessionData, (err) -> @@ -1400,14 +1392,14 @@ rpcResponse = (sessionData, id, rpcRes, callback) -> if rpcReq.nextLevel loadPath(sessionData, root, (1 << (root.length + 1)) - 1) else - loadPath(sessionData, root, (1 << config.get('MAX_DEPTH', sessionData.deviceId) - 1)) + loadPath(sessionData, root, (1 << config.get('MAX_DEPTH', sessionData.deviceId)) - 1) loadParameters(sessionData, (err) -> return callback(err) if err if root.length == 0 for n in ['DeviceID', 'Events', 'Tags', 'Reboot', 'FactoryReset', 'VirtualParameters'] - if p = sessionData.deviceData.paths.subset([n]).next().value + if p = sessionData.deviceData.paths.get([n]) if sessionData.deviceData.attributes.has(p) sessionData.deviceData.timestamps.set(p, timestamp) @@ -1546,9 +1538,7 @@ deserialize = (sessionDataString, callback) -> serialize = (sessionData, callback) -> deviceData = [] - iter = sessionData.deviceData.paths.find([], 99) - while not (p = iter.next()).done - path = p.value + for path in sessionData.deviceData.paths.find([], false, false, 99) e = [path] e[1] = sessionData.deviceData.loaded.get(path) || 0 e[2] = sessionData.deviceData.trackers.get(path) || null