Skip to content

Commit

Permalink
Much improved model performance with arrayBufferPool.
Browse files Browse the repository at this point in the history
it turns out that browsers have relatively slow fill methods,
so much that in some cases that can dominate the run time.
this commit introduces the lazy initialization combined with
the "marking" mechanism to ensure that the same array
can be used over and over without the miscompression.
now optimizer is from 20--30% to 3 times faster than before.

<benchmark>
samples used:
- default README inputs for the online demo (5.7 KB text)
- Edge Not Found (32.7 KB JS)

Firefox 92.0b4:
- README: 90s -> 30s
- Edge Not Found: 240s -> 130s

Chrome 92.0:
- README: 22s -> 16s
- Edge Not Found: 85s -> 70s
</benchmark>
  • Loading branch information
lifthrasiir committed Aug 23, 2021
1 parent 3bf7845 commit 8af149c
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 3 deletions.
31 changes: 29 additions & 2 deletions index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -188,14 +188,39 @@ export class DirectContextModel {
this.arrayBufferPool = arrayBufferPool;
this.predictions = newUintArray(arrayBufferPool, this, precision, 1 << contextBits);
this.counts = newUintArray(arrayBufferPool, this, Math.ceil(Math.log2(modelMaxCount + 1)), 1 << contextBits);
this.predictions.fill(1 << (precision - 1));
this.counts.fill(0);

if (arrayBufferPool) {
// we need to initialize the array since it may have been already used,
// but UintXXArray.fill is comparatively slow, less than 5 GB/s even in fastest browsers.
// we instead use more memory to confirm that each bit of context has been initialized.
//
// the final excess element is the maximum mark in use.
// (this kind of size is not used elsewhere, so we can safely reuse that.)
// we choose a new mark to mark initialized elements *in this instance*.
// if the mark reaches 255 we reset the entire array and start over.
// this scheme effectively reduces the number of fill calls by a factor of 510.
this.confirmations = newUintArray(arrayBufferPool, this, 8, (1 << contextBits) + 1);
this.mark = this.confirmations[1 << contextBits] + 1;
if (this.mark === 256) {
this.mark = 1;
this.confirmations.fill(0);
}
this.confirmations[1 << contextBits] = this.mark;
} else {
this.predictions.fill(1 << (precision - 1));
//this.counts.fill(0); // we don't really need this
}

this.bitContext = 1;
}

predict(context = 0) {
context = (context + this.bitContext) & ((1 << this.contextBits) - 1);
if (this.confirmations && this.confirmations[context] !== this.mark) {
this.confirmations[context] = this.mark;
this.predictions[context] = 1 << (this.precision - 1);
this.counts[context] = 0;
}
return this.predictions[context];
}

Expand Down Expand Up @@ -251,8 +276,10 @@ export class DirectContextModel {
if (this.arrayBufferPool) {
if (this.predictions) this.arrayBufferPool.release(this.predictions.buffer);
if (this.counts) this.arrayBufferPool.release(this.counts.buffer);
if (this.confirmations) this.arrayBufferPool.release(this.confirmations.buffer);
this.predictions = null;
this.counts = null;
this.confirmations = null;
}
}
}
Expand Down
28 changes: 27 additions & 1 deletion test.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import test from 'ava';
import * as crypto from 'crypto';
import {
AnsEncoder, AnsDecoder, DefaultModel, Packer,
ArrayBufferPool, AnsEncoder, AnsDecoder, DirectContextModel, DefaultModel, Packer,
compressWithModel, decompressWithModel
} from './index.mjs';

Expand Down Expand Up @@ -197,6 +197,32 @@ testCompressWithModel(veryRareZeroes, 'very rare zeroes', 15, SimpleModel);

//------------------------------------------------------------------------------

test('DirectContextModel.confirmations', t => {
const arrayBufferPool = new ArrayBufferPool();
const options = {
inBits: 8,
outBits: 8,
precision: 16,
contextBits: 5, // 32 elements
modelMaxCount: 63,
arrayBufferPool,
};

// the size of 1 will set ~8 elements and test for partial fills.
// the size of 10 will set all 32 elements with >92% probability and test for total fills.
for (const size of [1, 10]) {
// this ensures that we cycle through multiple confirmation resets
for (let i = 0; i < 1000; ++i) {
const input = [...crypto.randomBytes(size)];
const compressed = compressWithModel(input, new DirectContextModel(options), options);
const decompressed = decompressWithModel(compressed, new DirectContextModel(options), options);
t.deepEqual(decompressed, input);
}
}
});

//------------------------------------------------------------------------------

const testCode = testCompressWithModel.toString();

test('compress with DefaultModel', t => {
Expand Down

0 comments on commit 8af149c

Please sign in to comment.