【发布时间】:2021-07-11 06:17:37
【问题描述】:
我正在使用 Parse Server 并尝试加快使用布隆过滤器的查询。
每个文档都有一个字段bf,其数值范围为0...bloomSize,例如文档ID“xyz”被散列为bf = 6462
查询然后加载二进制布隆过滤器值,这些值被编码并保存在 base64 字符串中。为了在 Parse Server / MongoDB 中使用索引查询,我需要生成一个整数数组,然后可以将其与上述字段进行比较。因此需要对 base64 字符串进行解码,并且对于二进制数据中的每个 0,我必须附加该 0 值位置的整数。目前我正在使用以下 sn-p:
//loading [UInt8] from saved base64 string
const buf = new Buffer.from(request.user.get("daBlm"), 'base64');
var blm = Array()
for (var i = 0; i < buf.length; i++) {
//iterating through 8-bit chunks and convert each UInt8 into string
//as "toString" does not respect 8-bit length we chain ".padStart(8, '0')"
const byte = buf[i].toString(2).padStart(8, '0');
//loop through characters and for each 0, push the position into array
for (var l = 0; l < byte.length; l++) {
//push int to array for each bloom 0 value - not used bit
if (byte[l] == "0") {
blm.push(i*8 + l);
}
}
}
//at the end is the blm array user in containedIn query constraint that use indexes
dateQuery.containedIn("bf", blm);
虽然它按预期工作并且我的查询使用索引,但数组的生成是查询中最慢的部分。
我发现在进入循环之前设置数组长度会稍微加快速度:
//从保存的base64字符串加载[UInt8] const buf = new Buffer.from(request.user.get("daBlm"), 'base64');
var blm = Array()
blm.lenght = buf.lenght * 8 <-- this seems to help
for (var i = 0; i < buf.length; i++) {
//iterating through 8-bit chunks and convert each UInt8 into string
//as "toString" does not respect 8-bit length we chain ".padStart(8, '0')"
const byte = buf[i].toString(2).padStart(8, '0');
//loop through characters and for each 0, push the position into array
for (var l = 0; l < byte.length; l++) {
//push int to array for each bloom 0 value - not used bit
if (byte[l] == "0") {
blm.push(i*8 + l);
}
}
}
我没有注意到数组的const 和var 之间有任何区别。
我也尝试使用 switch 直接从 base64 生成,但这似乎比上面的循环慢了大约 10%-30%:
function availArr(base64Str) {
const lenght = base64Str.length;
if (lenght > 0) {
var arr = Array();
for (var i = 0; i < lenght; i++) {
//iterating through characters of base64String
switch (base64Str.charAt(i)) {
case "A": //000000
arr.push(...[i*8, i*8+1, i*8+2, i*8+3, i*8+4, i*8+5]);break;
case "B": //000001
arr.push(...[i*8, i*8+1, i*8+2, i*8+3, i*8+4]);break;
case "C": //000010
arr.push(...[i*8, i*8+1, i*8+2, i*8+3, i*8+5]);break;
case "D": //000011
arr.push(...[i*8, i*8+1, i*8+2, i*8+3]);break;
case "E": //000100
arr.push(...[i*8, i*8+1, i*8+2, i*8+4, i*8+5]);break;
case "F": //000101
arr.push(...[i*8, i*8+1, i*8+2, i*8+4]);break;
case "G": //000110
arr.push(...[i*8, i*8+1, i*8+2, i*8+5]);break;
case "H": //000111
arr.push(...[i*8, i*8+1, i*8+2]);break;
case "I": //001000
arr.push(...[i*8, i*8+1, i*8+3, i*8+4, i*8+5]);break;
case "J": //001001
arr.push(...[i*8, i*8+1, i*8+3, i*8+4]);break;
case "K": //001010
arr.push(...[i*8, i*8+1, i*8+3, i*8+5]);break;
case "L": //001011
arr.push(...[i*8, i*8+1, i*8+3]);break;
case "M": //001100
arr.push(...[i*8, i*8+1, i*8+4, i*8+5]);break;
case "N": //001101
arr.push(...[i*8, i*8+1, i*8+4]);break;
case "O": //001110
arr.push(...[i*8, i*8+1, i*8+5]);break;
case "P": //001111
arr.push(...[i*8, i*8+1]);break;
case "Q": //010000
arr.push(...[i*8, i*8+2, i*8+3, i*8+4, i*8+5]);break;
case "R": //010001
arr.push(...[i*8, i*8+2, i*8+3, i*8+4]);break;
case "S": //010010
arr.push(...[i*8, i*8+2, i*8+3, i*8+5]);break;
case "T": //010011
arr.push(...[i*8, i*8+2, i*8+3]);break;
case "U": //010100
arr.push(...[i*8, i*8+2, i*8+4, i*8+5]);break;
case "V": //010101
arr.push(...[i*8, i*8+2, i*8+4]);break;
case "W": //010110
arr.push(...[i*8, i*8+2, i*8+5]);break;
case "X": //010111
arr.push(...[i*8, i*8+2]);break;
case "Y": //011000
arr.push(...[i*8, i*8+3, i*8+4, i*8+5]);break;
case "Z": //011001
arr.push(...[i*8, i*8+3, i*8+4]);break;
case "a": //011010
arr.push(...[i*8, i*8+3, i*8+5]);break;
case "b": //011011
arr.push(...[i*8, i*8+3]);break;
case "c": //011100
arr.push(...[i*8, i*8+4, i*8+5]);break;
case "d": //011101
arr.push(...[i*8, i*8+4]);break;
case "e": //011110
arr.push(...[i*8, i*8+5]);break;
case "f": //011111
arr.push(...[i*8]);break;
case "g": //100000
arr.push(...[i*8+1, i*8+2, i*8+3, i*8+4, i*8+5]);break;
case "h": //100001
arr.push(...[i*8+1, i*8+2, i*8+3, i*8+4]);break;
case "i": //100010
arr.push(...[i*8+1, i*8+2, i*8+3, i*8+5]);break;
case "j": //100011
arr.push(...[i*8+1, i*8+2, i*8+3]);break;
case "k": //100100
arr.push(...[i*8+1, i*8+2, i*8+4, i*8+5]);break;
case "l": //100101
arr.push(...[i*8+1, i*8+2, i*8+4]);break;
case "m": //100110
arr.push(...[i*8+1, i*8+2, i*8+5]);break;
case "n": //100111
arr.push(...[i*8, i*8+1, i*8+2, i*8+3, i*8+4, i*8+5]);break;
case "o": //101000
arr.push(...[i*8+1, i*8+3, i*8+4, i*8+5]);break;
case "p": //101001
arr.push(...[i*8+1, i*8+3, i*8+4]);break;
case "q": //101010
arr.push(...[i*8+1, i*8+3, i*8+5]);break;
case "r": //101011
arr.push(...[i*8+1, i*8+3]);break;
case "s": //101100
arr.push(...[i*8+1, i*8+4, i*8+5]);break;
case "t": //101101
arr.push(...[i*8+1, i*8+4]);break;
case "u": //101110
arr.push(...[i*8+1, i*8+5]);break;
case "v": //101111
arr.push(...[i*8+1]);break;
case "w": //110000
arr.push(...[i*8+2, i*8+3, i*8+4, i*8+5]);break;
case "x": //110001
arr.push(...[i*8+2, i*8+3, i*8+4]);break;
case "y": //110010
arr.push(...[i*8+2, i*8+3, i*8+5]);break;
case "z": //110011
arr.push(...[i*8+2, i*8+3]);break;
case "0": //110100
arr.push(...[i*8+2, i*8+4, i*8+5]);break;
case "1": //110101
arr.push(...[i*8+2, i*8+4]);break;
case "2": //110110
arr.push(...[i*8+2, i*8+5]);break;
case "3": //110111
arr.push(...[i*8+2]);break;
case "4": //111000
arr.push(...[i*8+3, i*8+4, i*8+5]);break;
case "5": //111001
arr.push(...[i*8+3, i*8+4]);break;
case "6": //111010
arr.push(...[i*8+3, i*8+5]);break;
case "7": //111011
arr.push(...[i*8+3]);break;
case "8": //111100
arr.push(...[i*8+4, i*8+5]);break;
case "9": //111101
arr.push(...[i*8+4]);break;
case "+": //111110
arr.push(...[i*8+5]);break;
}
}
return arr;
}
return [];
}
有人知道如何加快速度吗?
EDIT2+3
我将函数移动到单独的代码中,它的行为保持不变,但允许我在其他函数中重用它。后来根据@Jonas 的建议,我试图避免字符串转换:
Buffer.prototype.toIntegerArray = function() {
const buffLenght = this.length;
if (buffLenght > 0) {
const arr = Array();
arr.lenght = buffLenght * 8
for (var i = 0; i < buffLenght; i++) {
//iterating through 8-bit chunks
for (var l = 0; l < 8; l++) {
//push int to array for each bloom 0 value - not used bit
//corrected syntax
if (((this[i] >> l) & 1) === 0) {
arr.push(i*8 + l);
}
}
}
return arr;
}
return [];
}
根据@Jonas 的评论,正确的语法确实找到了零。通过控制台时间戳测量查询的数组构建部分,平均 10 个查询响应,65536 位绽放大小全为 0(最终随机半盛开,对所有人都一样):
-
toString(2).padStart(8, '0')=> ~ 303ms (152ms) -
switch=> ~322ms (145ms) -
(((this[i] >> l) & 1) === 0)=> ~340ms -
(var mask = 0x80; mask > 0; mask>>=1)=> ~354ms -
if ((byte & 0x80) === 0) arr.push(counter);=> 314 毫秒 (158 毫秒)
正如我从测量结果中注意到的那样,循环遍历全 0s 数组和随机半开花有很大的不同。因此,我猜更大的部分是推动本身。
我了解测量结果部分具有误导性,但已给出环境(解析服务器云功能),遗憾的是我没有经验在那里进行更复杂的调查。越难成为 DBaaS 上的共享服务器。
【问题讨论】:
-
if((buf[i] >> l) & 1 === 0)还应该检查第 l 位是否为 0 -
作为另一个想法,base64 中的一个字符似乎总是被解码为 6 位二进制。因此可以生成一个从字符到零索引数组的查找表。遍历base64,查找并将这些结果推送到数组。
-
@Jonas 如果是您的第一条评论,我仍然不会摆脱这两个循环,对吧?如果我理解正确,它会将转换保存为字符串和 padStart?对于第二个想法,我读到 switch 的性能比查找表好,我尝试过这种方式,不幸的是性能并没有好多少。我会将其添加到我的问题中。
-
如果代码有效,您可以检查their help center 以查看您的问题是否与Code Review 的主题有关。
-
啊,我的第一条评论由于运算符优先级而不起作用,添加另一对大括号
(((this[i] >> l) & 1) === 0)
标签: javascript binary base64