JavaScript base64string -> 二进制值 -> [整数] ... 性能改进

JavaScript base64string -> binary values -> [Integer] ... performance improvement

我正在使用 Parse Server 并尝试加速使用布隆过滤器的查询。

每个文档都有一个字段 bf,数值在 0...bloomSize 范围内,例如文档 ID“xyz”被散列为 bf = 6462

查询然后加载编码并保存在 base64 字符串中的二进制布隆过滤器值。要在 Parse Server / MongoDB 中使用索引查询,我需要生成一个整数数组,然后我可以将其与上述字段进行比较。因此需要对 base64 字符串进行解码,并且对于二进制数据中的每个 0,我必须附加该 0 值位置的整数。目前我正在使用以下代码段:

//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);
        } 
    }
}

我没有注意到数组的 constvar 之间有任何区别。

我也尝试用 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 个查询响应,所有 0 的 65536 位布隆大小(最终随机半全布隆,所有布隆相同):

  1. toString(2).padStart(8, '0') => ~ 303 毫秒 (152 毫秒)
  2. switch => ~322 毫秒(145 毫秒)
  3. (((this[i] >> l) & 1) === 0) => ~340 毫秒
  4. (var mask = 0x80; mask > 0; mask>>=1) => ~354ms
  5. if ((byte & 0x80) === 0) arr.push(counter); => 314 毫秒(158 毫秒)

正如我从测量中注意到的那样,循环遍历全 0 数组和随机半盛放有很大的不同。因此,我猜更大的部分会自己推动。

我知道测量有部分误导性,但环境是给定的(Parse Server 云功能),不幸的是,我没有那么丰富的经验,无法在那里进行更复杂的调查。越难是DBaaS上的共享服务器

当您避免使用 .toString(2) 转换为字符串时,它应该会有所改善。也可以通过使用单独的计数器变量来避免重复的 i*8+l

for (var i = 0, counter = 0; i < buf.length; i++) {
    var byte = buf[i];
    for (var mask = 0x80; mask > 0; mask>>=1) {
        if ((byte & mask) === 0) {
            blm.push(counter);
        }
        counter++;
    }
}

由于内部循环只有 8 次迭代,您也可以将代码复制 8 次,从而减少移位操作:

for (var i = 0, counter = 0; i < buf.length; i++, counter+=8) {
    var byte = buf[i];
    if ((byte & 0x80) === 0) blm.push(counter);
    if ((byte & 0x40) === 0) blm.push(counter+1);
    if ((byte & 0x20) === 0) blm.push(counter+2);
    if ((byte & 0x10) === 0) blm.push(counter+3);
    if ((byte & 0x08) === 0) blm.push(counter+4);
    if ((byte & 0x04) === 0) blm.push(counter+5);
    if ((byte & 0x02) === 0) blm.push(counter+6);
    if ((byte & 0x01) === 0) blm.push(counter+7);
}

跳过 base64 解码的想法也是一个很好的尝试,尽管这意味着在纯 JavaScript 中有更长的字符串要迭代(而不是在更低、更快的级别上发生这种情况)。尽管如此,在您的尝试中仍有一些需要改进的地方:构建一个数组然后传播它是浪费时间。您可以将这些参数直接传递给 push。例如:

case "A": //000000
    arr.push(i*8, i*8+1, i*8+2, i*8+3, i*8+4, i*8+5);break;

此外,应避免重复 i*8(与上述相同的解决方案)。