Optimize PathSet for V8

PathSet.find has been rewritten without using ES6 generators in order
to avoid V8's TurboFan/Crankshift optimization bailout.
This commit is contained in:
Zaid Abdulla committed 2017-02-15 02:19:02 -08:00
1 parent a48984b909
commit 3223e9733a
5 files changed
+115 -138

No files matched your search

+1 -2
View File
@@ -244,8 +244,7 @@ applyPresets = (currentRequest) ->
sessionData.presetsHash = presetsHash sessionData.presetsHash = presetsHash
deviceEvents = {} deviceEvents = {}
iter = sessionData.deviceData.paths.subset(['Events', '*']) for p in sessionData.deviceData.paths.find(['Events', '*'], false, true)
while (p = iter.next().value)
if sessionData.timestamp <= sessionData.deviceData.attributes.get(p)?.value?[1][0] if sessionData.timestamp <= sessionData.deviceData.attributes.get(p)?.value?[1][0]
deviceEvents[p[1]] = true deviceEvents[p[1]] = true
+1 -1
View File
@@ -339,7 +339,7 @@ saveDevice = (deviceId, deviceData, isNew, callback) ->
if not diff[2]? and diff[1]? if not diff[2]? and diff[1]?
update['$unset'][diff[0].slice(0, -1).concat('_timestamp').join('.')] = 1 update['$unset'][diff[0].slice(0, -1).concat('_timestamp').join('.')] = 1
else if diff[2] != diff[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)) 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]) update['$set'][diff[0].slice(0, -1).concat('_timestamp').join('.')] = new Date(diff[2])
+5 -5
View File
@@ -96,7 +96,7 @@ function getAliasDeclarations(path, timestamp) {
function unpack(deviceData, path, revision) { function unpack(deviceData, path, revision) {
let allMatches = []; let allMatches = [];
if (!path.alias) { 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)) if (deviceData.attributes.has(p, revision))
allMatches.push(p); allMatches.push(p);
} }
@@ -109,7 +109,7 @@ function unpack(deviceData, path, revision) {
wildcardPath.wildcard |= 1 << i; 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)) if (deviceData.attributes.has(p, revision))
allMatches.push(p); allMatches.push(p);
@@ -169,7 +169,7 @@ function unpack(deviceData, path, revision) {
function clear(deviceData, path, timestamp, attributes) { 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)) for (let k in deviceData.trackers.get(p))
deviceData.changes.add(k); deviceData.changes.add(k);
@@ -279,7 +279,7 @@ function set(deviceData, path, timestamp, attributes, toClear) {
} }
if (changeFlags) 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); let c = deviceData.trackers.get(sup);
for (let k in c) for (let k in c)
if (c[k] & changeFlags) { if (c[k] & changeFlags) {
@@ -309,7 +309,7 @@ function set(deviceData, path, timestamp, attributes, toClear) {
} }
if (path.wildcard) 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)) { if (timestamp > deviceData.timestamps.get(sub)) {
deviceData.timestamps.delete(sub); deviceData.timestamps.delete(sub);
if (deviceData.attributes.delete(sub)) { if (deviceData.attributes.delete(sub)) {
+91 -103
View File
@@ -31,60 +31,28 @@ class PathSet {
add(path) { add(path) {
let sets; let p = this.get(path);
if (p)
return p;
if (!this.lengthIndex[path.length]) if (!this.lengthIndex[path.length])
this.lengthIndex[path.length] = new Set(); 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); this.lengthIndex[path.length].add(path);
let wildcard = 0;
for (let i = 0; i < path.length; ++ i) { for (let i = 0; i < path.length; ++ i) {
let fragment = path[i]; let fragment = path[i];
if (fragment === '*') if (fragment === "*")
wildcard |= 1 << i; 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); this.fragmentIndex[i][fragment].add(path);
} }
@@ -97,90 +65,110 @@ class PathSet {
} }
*subset(path, depth) { get(pattern) {
var p = path.slice(); if (!this.lengthIndex[pattern.length])
for (let i = 0; i < p.length; ++ i) return null;
if (p[i] === '*')
p[i] = null;
yield* this.find(p, depth || 0, false);
}
if (this.lengthIndex[pattern.length].has(pattern))
return pattern;
*superset(path, depth) { let sets = [];
yield* this.find(path, depth || 0, true)
}
sets.push(this.lengthIndex[pattern.length]);
*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);
for (let i = 0; i < pattern.length; ++ i) { for (let i = 0; i < pattern.length; ++ i) {
if (pattern[i]) { if (!this.fragmentIndex[i])
let fragment = pattern[i]; return null;
let s = [];
if (this.fragmentIndex[i]) { let fragment = pattern[i];
if (this.fragmentIndex[i][fragment])
s.push(this.fragmentIndex[i][fragment]);
if (wildcard && fragment != '*' && this.fragmentIndex[i]['*']) if (!this.fragmentIndex[i][fragment])
s.push(this.fragmentIndex[i]['*']) return null;
}
if (!s.length) sets.push(this.fragmentIndex[i][fragment]);
return;
sets.push(s);
}
} }
sets.sort(function(a, b) { 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) - return a.reduce(function(prev, cur) {return prev + cur.size;}, 0) -
b.reduce(function(prev, cur) {return prev + cur.size;}, 0); b.reduce(function(prev, cur) {return prev + cur.size;}, 0);
}); });
var smallest = sets.shift(); let smallestGroup = groups.shift();
for (let pp of smallest) { for (let set of smallestGroup) {
for (let p of pp) { for (let path of set) {
for (let ss of sets) { for (let group of groups) {
let found = false; let found = false;
for (let s of ss) { for (let s of group) {
if (s.has(p)) { if (s.has(path)) {
found = true; found = true;
break; break;
} }
} }
if (!found) { if (!found) {
p = null; path = null;
break; break;
} }
} }
if (path)
if (p) res.push(path);
yield p;
} }
} }
return res;
} }
} }
+17 -27
View File
@@ -671,8 +671,7 @@ rpcRequest = (sessionData, _declarations, callback) ->
else if c? and not v else if c? and not v
toClear = device.set(sessionData.deviceData, p, timestamp, toClear) toClear = device.set(sessionData.deviceData, p, timestamp, toClear)
noMoreTags = true noMoreTags = true
iter = sessionData.deviceData.paths.subset(['Tags', '*']) for p in sessionData.deviceData.paths.find(['Tags', '*'], false, true)
while p = iter.next().value
if sessionData.deviceData.attributes.has(p) if sessionData.deviceData.attributes.has(p)
noMoreTags = false noMoreTags = false
break break
@@ -685,8 +684,7 @@ rpcRequest = (sessionData, _declarations, callback) ->
sessionData.syncState.downloadsToCreate.forEach((instance) -> sessionData.syncState.downloadsToCreate.forEach((instance) ->
if not index? if not index?
index = 0 index = 0
iter = sessionData.deviceData.paths.subset(['Downloads', '*']) for p in sessionData.deviceData.paths.find(['Downloads', '*'], false, true)
while p = iter.next().value
if +p[1] > index and sessionData.deviceData.attributes.has(p) if +p[1] > index and sessionData.deviceData.attributes.has(p)
index = +p[1] index = +p[1]
@@ -831,9 +829,8 @@ generateGetRpcRequest = (sessionData) ->
iter = syncState.refreshAttributes.exist.values() iter = syncState.refreshAttributes.exist.values()
while (path = iter.next().value) while (path = iter.next().value)
descendantIter = sessionData.deviceData.paths.subset(path, 99)
found = false 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 if syncState.refreshAttributes.value.has(p) or
syncState.refreshAttributes.object.has(p) or syncState.refreshAttributes.object.has(p) or
syncState.refreshAttributes.writable.has(p) or syncState.refreshAttributes.writable.has(p) or
@@ -849,9 +846,8 @@ generateGetRpcRequest = (sessionData) ->
iter = syncState.refreshAttributes.object.values() iter = syncState.refreshAttributes.object.values()
while (path = iter.next().value) while (path = iter.next().value)
descendantIter = sessionData.deviceData.paths.subset(path, 99)
found = false 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 if syncState.refreshAttributes.value.has(p) or
(p.length > path.length and (p.length > path.length and
(syncState.refreshAttributes.object.has(p) or (syncState.refreshAttributes.object.has(p) or
@@ -885,8 +881,7 @@ generateGetRpcRequest = (sessionData) ->
if path if path
patterns = [] patterns = []
iter = sessionData.deviceData.paths.superset(path, 99) for p in sessionData.deviceData.paths.find(path, true, false, 99)
while p = iter.next().value
if v = syncState.gpnPatterns.get(p) if v = syncState.gpnPatterns.get(p)
patterns.push([p, (v >> path.length) << path.length]) patterns.push([p, (v >> path.length) << path.length])
@@ -900,8 +895,7 @@ generateGetRpcRequest = (sessionData) ->
syncState.gpn.delete(path) syncState.gpn.delete(path)
else else
nextLevel = false nextLevel = false
iter = sessionData.deviceData.paths.subset(path, 99) for p in sessionData.deviceData.paths.find(path, false, true, 99)
while p = iter.next().value
syncState.gpn.delete(p) syncState.gpn.delete(p)
return { return {
@@ -985,7 +979,7 @@ generateSetRpcRequest = (sessionData) ->
# Reboot # Reboot
if syncState.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) if not (p? and sessionData.deviceData.attributes.get(p)?.value?[1][0] >= syncState.reboot)
delete syncState.reboot delete syncState.reboot
return { return {
@@ -994,7 +988,7 @@ generateSetRpcRequest = (sessionData) ->
# Factory reset # Factory reset
if syncState.factoryReset? 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) if not (p? and sessionData.deviceData.attributes.get(p)?.value?[1][0] >= syncState.factoryReset)
delete syncState.factoryReset delete syncState.factoryReset
return { return {
@@ -1005,9 +999,9 @@ generateSetRpcRequest = (sessionData) ->
iter = syncState.downloadsDownload.entries() iter = syncState.downloadsDownload.entries()
while pair = iter.next().value while pair = iter.next().value
if not (pair[1] <= deviceData.attributes.get(pair[0])?.value?[1][0]) 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 fileTypePath = deviceData.paths.get(pair[0].slice(0, -1).concat('FileType'))
fileNamePath = deviceData.paths.subset(pair[0].slice(0, -1).concat('FileName')).next().value fileNamePath = deviceData.paths.get(pair[0].slice(0, -1).concat('FileName'))
targetFileNamePath = deviceData.paths.subset(pair[0].slice(0, -1).concat('TargetFileName')).next().value targetFileNamePath = deviceData.paths.get(pair[0].slice(0, -1).concat('TargetFileName'))
return { return {
type: 'Download' type: 'Download'
commandKey: generateRpcId(sessionData) commandKey: generateRpcId(sessionData)
@@ -1054,7 +1048,7 @@ processDeclarations = (sessionData, allDeclareTimestamps, allDeclareAttributeTim
syncState = sessionData.syncState syncState = sessionData.syncState
root = sessionData.deviceData.paths.add([]) 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) -> paths.sort((a, b) ->
if a.wildcard == b.wildcard if a.wildcard == b.wildcard
return a.length - b.length return a.length - b.length
@@ -1196,9 +1190,7 @@ loadPath = (sessionData, path, depth) ->
path = path.slice(0, trimWildcard) if trimWildcard < path.length path = path.slice(0, trimWildcard) if trimWildcard < path.length
for i in [0..path.length] by 1 for i in [0..path.length] by 1
iter = sessionData.deviceData.paths.superset(path.slice(0, i)) for sup in sessionData.deviceData.paths.find(path.slice(0, i), true, false)
while sup = iter.next().value
v = sessionData.deviceData.loaded.get(sup) v = sessionData.deviceData.loaded.get(sup)
depth &= depth ^ 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) MAX_DEPTH = config.get('MAX_DEPTH', sessionData.deviceId)
toClear.forEach((c) -> 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) -> loadParameters(sessionData, (err) ->
@@ -1400,14 +1392,14 @@ rpcResponse = (sessionData, id, rpcRes, callback) ->
if rpcReq.nextLevel if rpcReq.nextLevel
loadPath(sessionData, root, (1 << (root.length + 1)) - 1) loadPath(sessionData, root, (1 << (root.length + 1)) - 1)
else 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) -> loadParameters(sessionData, (err) ->
return callback(err) if err return callback(err) if err
if root.length == 0 if root.length == 0
for n in ['DeviceID', 'Events', 'Tags', 'Reboot', 'FactoryReset', 'VirtualParameters'] 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) if sessionData.deviceData.attributes.has(p)
sessionData.deviceData.timestamps.set(p, timestamp) sessionData.deviceData.timestamps.set(p, timestamp)
@@ -1546,9 +1538,7 @@ deserialize = (sessionDataString, callback) ->
serialize = (sessionData, callback) -> serialize = (sessionData, callback) ->
deviceData = [] deviceData = []
iter = sessionData.deviceData.paths.find([], 99) for path in sessionData.deviceData.paths.find([], false, false, 99)
while not (p = iter.next()).done
path = p.value
e = [path] e = [path]
e[1] = sessionData.deviceData.loaded.get(path) || 0 e[1] = sessionData.deviceData.loaded.get(path) || 0
e[2] = sessionData.deviceData.trackers.get(path) || null e[2] = sessionData.deviceData.trackers.get(path) || null