首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >JavaScript BitArray实现

JavaScript BitArray实现
EN

Code Review用户
提问于 2022-10-08 17:32:29
回答 1查看 172关注 0票数 3

我正在做一个项目,在这个项目中,数组的长度可以很容易地超过50米。它是一个只保存布尔值(0/1)值的数组。使用Uint8Array很好,与普通数组相比,它的性能非常好。不过,我还是想尝试通过扩展BitArray对象来实现DataView。与普通数组(1/32)和Uint8Array (1/8)相比,D6大大减少了内存占用。因此,这是一个一定的增益,但就速度而言,尽管我已经尽了一切努力,BitArray仍然比两者都慢一些,除非大小达到33,554,433个极限(> 2⁵)。正如我从这份文件中读到的,此时正常的Array达到了~268 v8的内存限制,它的内部结构被转换为NumberDictionary[16],在v8中产生了显著的减速。注意,当长度为33,554,433时,BitArray只使用4MB内存。

另外一点要注意的是,对于我的应用程序,我需要BitArray (人口计数)中的1s总数,这是下面代码中的popcount属性。由于速度快得惊人,Hamming权算法 BitArray比计算数组中的现有项快80倍,尽管我在这里没有测试它。

任何提振BitArray的想法都是最受欢迎的。

代码语言:javascript
复制
class BitArray extends DataView{

  constructor(n,ab){
    var abs = n >> 3; // ArrayBuffer Size
    super(ab instanceof ArrayBuffer ? ab
                                    : n & 31 ? new ArrayBuffer(abs + 4 - (abs & 3))
                                             : new ArrayBuffer(abs));
  }

  get length(){
    return this.buffer.byteLength*8;
  }

  get popcount(){
    var m1  = 0x55555555,
        m2  = 0x33333333,
        m4  = 0x0f0f0f0f,
        h01 = 0x01010101,
        pc  = 0,
        x;
    for (var i = 0, len = this.buffer.byteLength >> 2; i < len; i++){
       x = this.getUint32(i << 2);
       x -= (x >> 1) & m1;             //put count of each 2 bits into those 2 bits
       x = (x & m2) + ((x >> 2) & m2); //put count of each 4 bits into those 4 bits
       x = (x + (x >> 4)) & m4;        //put count of each 8 bits into those 8 bits
       pc += (x * h01) >> 56;
    }
    return pc;
  }

  // n >> 3 is Math.floor(n/8)
  // n & 7 is n % 8

  at(n){  
    return this.getUint8(n >> 3) & (1 << (n & 7)) ? 1 : 0;
  }

  set(n){
    this.setUint8(n >> 3, this.getUint8(n >> 3) | (1 << (n & 7)));
  }

  reset(n){
    this.setUint8(n >> 3, this.getUint8(n >> 3) & ~(1 << (n & 7)));
  }

  slice(a = 0, b = this.length){
    return new BitArray(b-a,this.buffer.slice(a >> 3, b >> 3));
  }

  toggle(n){
    this.setUint8(n >> 3, this.getUint8(n >> 3) ^ (1 << (n & 7)));
  }

  toString(){
    return new Uint8Array(this.buffer).reduce((p,c) => p + Array.prototype.reduce.call(c.toString(2).padStart(8,"0"),(f,s) => s+f), "")
                                      .slice(0,this.length);
  }
}

// Test code starts from here

var len = 1e6,  // array length
    tst = 10,   // test count
    arr = Array(len),
    bar = new BitArray(len),
    uia = new Uint8Array(len),
    r1,r2,r3,t = 0;

console.log(`There are ${bar.popcount} 1s in the BitArray`);
for (var i = 0; i < len; i++){
  (Math.random() > 0.5) && ( t++
                           , bar.set(i)
                           );
}

console.log(`${t} .set() ops are made and now there are ${bar.popcount} 1s in the BitArray`);

console.time("Array");
for (var k = 0; k < tst; k++){
  for (var i = 0; i < len; i++) arr[i] = Math.random() > 0.5 ? 1 : 0;
  for (var i = 0; i < len; i++) t = arr[1];
  r1 = arr.slice();
}
console.timeEnd("Array");

console.time("BitArray");
for (var k = 0; k < tst; k++){
  for (var i = 0; i < len; i++) Math.random() > 0.5 ? bar.set(i) : bar.reset(i)
  for (var i = 0; i < len; i++) t = bar.at(i);
  r2 = bar.slice();
}
console.timeEnd("BitArray");

console.time("Uint8Array");
for (var k = 0; k < tst; k++){
  for (var i = 0; i < len; i++) uia[i] = Math.random() > 0.5 ? 1 : 0;
  for (var i = 0; i < len; i++) t = uia[i];
  r3 = uia.slice();
}
console.timeEnd("Uint8Array");
EN

回答 1

Code Review用户

回答已采纳

发布于 2022-10-16 17:05:50

abs + 4 - (abs & 3)

可以简化:(abs + 3) & -4

它们不是等价表达式,但当abs不是4的倍数时,它们是等价的。实际上,在这种情况下,更简单的表达式更方便:使用这个表达式,您可以跳过三元表达式,因为它不会不适当地将4的倍数集合起来,因此不再是特例。

popcount诡计

你已经使用了一个很好的技巧,但是还有更多的可能。这个技巧的一个有趣的特点是,当它完成了几个步骤之后,它就开始浪费它的一些潜力,例如,我的意思是,第三步(x + (x >> 4)) & m4添加相邻的咬口,但是这些小块中的值从0到4不等(包括),而不是0到15的范围。如果循环被展开为2的因子,那么只需要复制前半部分:然后可以添加中间结果(产生的值范围为0到8,包括在内)。技巧的后半部分(第三和第四步)只需要执行一次。

还有更多的技巧,例如,给定3位uint32s (a, b, c),我们只需要2位32位波普计数就可以计算所有比特:

代码语言:javascript
复制
b0 = a ^ b ^ c
b1 = (a & (b | c)) | (b & c)
count = popcount(b0) + 2 * popcount(b1)

位操作步骤实际上是位级3输入加法,2位结果分布于两个变量:在每个位置,b0持有该位置之和的0位,b1持有之和的第1位。这可以概括为将7个输入组合成3,15到4,等等。在使用AVX2 2指令进行更快的填充计数中可以找到更好的版本(当然,您不会在JavaScript中使用AVX2,但是可以使用一些相同的技术来减少实际的流行次数)。

票数 1
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/280299

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档