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:
1 parent
a48984b909
commit
3223e9733a
5 files changed
+116
-139
No files matched your search
+1
-2
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Reference in new issue
Block a user