Repository: brockwhittaker/BitArray.js Branch: master Commit: 89c4a6a54404 Files: 4 Total size: 9.0 KB Directory structure: gitextract_0gvc6z21/ ├── bits.js ├── perf-test.js ├── readme.md └── tests.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: bits.js ================================================ const BitArray = (() => { // The BitArray is a simple bit flag implementation that utilizes standard arrays // (for expandability) and Uint32Array types for memory efficiency. // Every value is assumed either true or false (inclusive of uninitialized values), // however a warning is given if a particular block does not exist yet. const BitArray = function (opts) { // if the opts aren't specified, let's just make it an object so we can // safely OR its properties below. if (!opts) { opts = {}; } // an internal map of Uint32Array blocks. // this has the fault of becoming a hole-d array, which could be bad. // perhaps give the option to use objects rather than arrays? this.map = []; // the size of a block inside of the map of Uint32Array objects. this.binSize = opts.binSize || 100; }; BitArray.prototype = { // get a flag by its ID. get: function (id) { let b = this.binSize; // faster than `Math.floor`. // this gets the zone (bin) of Uint32Arrays (primary array index). let z = ~~(id / (b * 32)); // if a value is inside a block that has already been created, we cannot // *really* tell whether a value has been set, but if the block doesn't // exist yet then it definitely doesn't. if (!this.map[z]) { console.warn(`A value at the index '${id}' does not exist.`); return false; } // the bin of Uint32Array objects that we're looking in. let bin = this.map[z]; // the actual Uint32Array the boolean exists within. // secondary array index. let slot = ~~((id % (b * 32)) / 32); // id & 31 is equivalent to id % 32 // 1 << n is equal to 2 ^ x // // for any mod n where n = 2^i, modulo is simply keeping the lower order // bits 0 through i - 1 // 00100100 | 36 // AND 00011111 | 31 // ==> 00000100 | 4 return !!(bin[slot] & (1 << (id & 31))); }, // set a Boolean value into an ID. set: function (id, val) { // this code is copied from above because this function call is expensive // (adds 20%) and can't really be inlined by JIT. let b = this.binSize; let z = ~~(id / (b * 32)); if (!this.map[z]) { this.map[z] = new Uint32Array(b); } let bin = this.map[z]; let slot = ~~((id % (b * 32)) / 32); if (val) { // if the value is truthy, use |= which will OR update the flag. // 01010001 // OR 00100000 // ==> 01110001 bin[slot] |= 1 << (id & 31); } else { // if the value is falsy, use &= ~FLAG to negate the flag. // 01010001 // AND 11101111 // ==> 01000001 bin[slot] &= ~(1 << (id & 31)); } return bin[slot]; }, // flip a bit to the opposite of its current value. // this will initialize flags that don't exist yet and set them to "true". flip: function (id) { let b = this.binSize; let z = ~~(id / (b * 32)); if (!this.map[z]) { this.map[z] = new Uint32Array(b); } let bin = this.map[z]; let slot = ~~((id % (b * 32)) / 32); // flip the particular flag. // 01000100 // FLIP 00000100 // ===> 01000000 bin[slot] ^= 1 << (id & 31); return bin[slot]; } }; return BitArray; })(); if (typeof module !== "undefined" && module.exports) { module.exports = BitArray; } ================================================ FILE: perf-test.js ================================================ const v8 = require("v8"), BitArray = require("./bits"); let compare = { standard: () => { const arr = []; for (let x = 0; x < 1e7; x++) { arr[x] = !!Math.round(Math.random()); } return arr; }, fixedArray: () => { const arr = new Array(1e7); for (let x = 0; x < 1e7; x++) { arr[x] = !!Math.round(Math.random()); } return arr; }, Uint8Array: () => { const arr = new Uint8Array(1e7); for (let x = 0; x < 1e7; x++) { arr[x] = !!Math.round(Math.random()); } return arr; }, bitArray: () => { const arr = new BitArray({ binSize: 1000 }); for (let x = 0; x < 1e7; x++) { arr.set(x, !!Math.round(Math.random())); } return arr; } }; const TEST = "bitArray"; const s1 = v8.getHeapStatistics(), d = new Date(); let arr = compare[TEST](); console.log(new Date() - d, v8.getHeapStatistics().total_heap_size - s1.total_heap_size); ================================================ FILE: readme.md ================================================ # BitArray.js BitArray.js is a micro-library that creates an array of booleans with less than 2% of the memory consumption of an array of booleans. BitArray weighs in at just 0.85kb compressed and 0.35kb gzipped, which makes it extremely lightweight to add to any project. ## Tests There are three tests for speed vs. memory consumption: 1. Creating an array with 10m elements and adding a boolean to each index. ```js const arr = []; for (let x = 0; x < 1e7; x++) { arr[x] = !!Math.round(Math.random()); } return arr; ``` 2. Creating an array with a pre-assigned length of 10m and adding a boolean to each index. ```js const arr = new Array(1e7); for (let x = 0; x < 1e7; x++) { arr[x] = !!Math.round(Math.random()); } return arr; ``` 3. Testing the BitArray. ```js const arr = new BitArray(); for (let x = 0; x < 1e7; x++) { arr.set(x, !!Math.round(Math.random())); } return arr; ``` ### Results | Structure | Time | Heap Size | % Memory | | -------------- | ----- | ---------- | -------- | | Dynamic Array | 740ms | 335,155,200 | 100% | | Pre-Init Array | 267ms | 81,068,032 | 24.19% | | BitArray       | 479ms | 4,194,304 | 1.25% | ## Methods The BitArray class only has three methods currently: `get`, `set`, and `flip`. ## Usage Below is a quick demo of all the cases this can be used with: ```js let array = new BitArray(); array.get(0); // false array.set(0, true); // true array.get(0); // true array.flip(0); // false ``` ## Under the Hood The booleans are stored in a nested array with the signature of `Array`. The array is flexible in length whereas the `Uint32Array` by default is fixed. Each boolean is stored as a bit inside of a 32-bit integer. ## Customization One option is given, which is `binSize`. This is the size of a `Uint32Array` (a bin of integers). With smaller sets of only a few dozen booleans it would be beneficial to use a small bin size like 1 or 2. When dealing with millions of booleans however, you can do some napkin math to figure out how large the `Uint32Array` should be (given that less separate arrays means less seperate overhead and pointers in memory). Each integer fits 32 booleans, so a bin of 100 `Uint32Array` objects would be capable of storing 3,200 flags. ### Example Below is an example of setting the binSize to be 1000: ```js let binSize = 1e3; const array = new BitArray({ binSize }); ``` ## Contribute If you have a contribution you'd like to make, submit a pull request! Try to match the style of the code written – it should have the appearance of being written by a single author. ================================================ FILE: tests.js ================================================ const BitArray = require("./bits"); const Test = () => { const results = { pass: 0, fail: 0 }; console.log("\nStarting tests...\n"); return { assert: { equal: (v1, v2) => { let flag = v1 === v2, str = flag ? `SUCCESS: ${v1} === ${v2}` : `ERROR: ${v1} !== ${v2}`; if (flag) results.pass++; else results.fail++; console.log(str); if (!flag) { let err = new Error().stack; console.log(err.split(/\n/)[2]); } } }, results: () => { console.log(`\nPASSED: ${results.pass}\nFAILED: ${results.fail}\n`); if (results.pass && !results.fail) { console.log("All tests passed successfully!\n"); } } }; } const bits = new BitArray(), test = Test(); // simple bit initialization. bits.set(0, true); bits.set(1, false); bits.set(2000, true); bits.set(3, true); test.assert.equal(bits.get(0), true); test.assert.equal(bits.get(1), false); test.assert.equal(bits.get(2000), true); test.assert.equal(bits.get(3), true); // test flipping the value of the bit. bits.flip(3); // test multi-resetting of bit. bits.set(234809, true); bits.set(234809, false); bits.flip(234809); test.assert.equal(bits.get(3), false); test.assert.equal(bits.get(234809), true); console.log(test.results());