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
+116 -139

No files matched your search

+1 -2
View File
@@ -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
+1 -1
View File
@@ -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])
+5 -5
View File
@@ -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)) {
+92 -104
View File
@@ -31,60 +31,28 @@ class PathSet {
add(path) {
let sets;
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;
}
}
let p = this.get(path);
if (p)
return p;
}
}
var wildcard = 0;
if (!this.lengthIndex[path.length])
this.lengthIndex[path.length] = new Set();
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]) {
if (!this.fragmentIndex[i])
return null;
let fragment = pattern[i];
let s = [];
if (this.fragmentIndex[i]) {
if (this.fragmentIndex[i][fragment])
s.push(this.fragmentIndex[i][fragment]);
if (!this.fragmentIndex[i][fragment])
return null;
if (wildcard && fragment != '*' && this.fragmentIndex[i]['*'])
s.push(this.fragmentIndex[i]['*'])
}
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 (path)
res.push(path);
}
}
if (p)
yield p;
}
}
return res;
}
}
+17 -27
View File
@@ -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