I am working on a project in which an array can easily grow beyond 50M in length. It's an array holding only boolean (0
/1
) values. Using Uint8Array
is just fine and it's very performant compared normal arrays. Still I just wanted to try implementing a BitArray
by extending DataView
object. BitArray
enormously reduces the memory footprint compared to normal Arrays (1/32) and Uint8Array
(1/8). So that's a certain gain but as for speed, despite I did everything to my knowledge BitArray
is still slightly slower compared to both unless the size hits the 33,554,433 limit (> 2²⁵). As I have read from this document, at this point the normal Array
reaches to ~268MB memory limit and it's internal structure gets switched to NumberDictionary[16]
yielding a dramatic slow down in v8. Note that when the length is 33,554,433 the BitArray
uses only 4MB of memory.
One other point to note is, for my application i need the total number of 1s in the BitArray
(population count) which is the popcount
property in below code. Thanks to the blazingly fast Hamming Weight algorithmBitArray
is like 80 times faster compared to counting the existing items in array though I am not testing it here.
Any ideas to boost the BitArray
up a little are most welcome.
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");