version bump 0.2.0: performance

- used Int32Array for storing CRC32 table when possible
- buf/array impl 4-step unrolling
- integration performance tests
This commit is contained in:
SheetJS 2014-06-17 19:20:41 -04:00
parent 606138a7e9
commit c79a8fb6c9
14 changed files with 367 additions and 30 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
node_modules
misc/coverage.html
misc/*/

View File

@ -51,3 +51,7 @@ coveralls-spin:
.PHONY: perf
perf:
bash perf/perf.sh
.PHONY: perf-all
perf-all:
bash misc/perf.sh

View File

@ -33,7 +33,12 @@ run `make ctest`.
## Performance
`make perf` will run performance tests.
`make perf` will run algorithmic performance tests (which should justify certain
decisions in the code).
`make perf-all` compares the performance of various crc-32 algorithms that
implement the correct form (note that the SSE intrinsic is designed for the
CRC32C checksum and uses a different polynomial).
Unexpected code patterns were based on performance testing in node and browser:

View File

@ -1 +1 @@
CRC32.version = '0.1.0';
CRC32.version = '0.2.0';

View File

@ -15,7 +15,7 @@ function signed_crc_table() {
table[n] = c;
}
return table;
return typeof Int32Array !== 'undefined' ? new Int32Array(table) : table;
}
var table = signed_crc_table();

View File

@ -1,17 +1,29 @@
/* charCodeAt is the best approach for binary strings */
var use_buffer = typeof Buffer !== 'undefined';
function crc32_bstr(bstr) {
for(var crc = -1, i = 0, L=bstr.length-1; i < L;) {
crc = (crc >>> 8) ^ table[(crc ^ bstr.charCodeAt(i++)) & 0xFF];
crc = (crc >>> 8) ^ table[(crc ^ bstr.charCodeAt(i++)) & 0xFF];
if(bstr.length > 32768) if(use_buffer) return crc32_buf(Buffer(bstr));
var crc = -1, L = bstr.length - 1;
for(var i = 0; i < L;) {
crc = table[(crc ^ bstr.charCodeAt(i++)) & 0xFF] ^ (crc >>> 8);
crc = table[(crc ^ bstr.charCodeAt(i++)) & 0xFF] ^ (crc >>> 8);
}
if(i === L) crc = (crc >>> 8) ^ table[(crc ^ bstr.charCodeAt(i++)) & 0xFF];
if(i === L) crc = (crc >>> 8) ^ table[(crc ^ bstr.charCodeAt(i)) & 0xFF];
return crc ^ -1;
}
function crc32_buf(buf) {
for(var crc = -1, i = 0; i != buf.length; ++i) {
crc = (crc >>> 8) ^ table[(crc ^ buf[i]) & 0xFF];
for(var crc = -1, i = 0, L=buf.length-3; i < L;) {
crc = (crc >>> 8) ^ table[(crc^buf[i++])&0xFF];
crc = (crc >>> 8) ^ table[(crc^buf[i++])&0xFF];
crc = (crc >>> 8) ^ table[(crc^buf[i++])&0xFF];
crc = (crc >>> 8) ^ table[(crc^buf[i++])&0xFF];
}
if(i < L+3) {
crc = (crc >>> 8) ^ table[(crc^buf[i++])&0xFF];
if(i < L+3) {
crc = (crc >>> 8) ^ table[(crc^buf[i++])&0xFF];
if(i < L+3) {
crc = (crc >>> 8) ^ table[(crc^buf[i++])&0xFF]; }}}
return crc ^ -1;
}

View File

@ -2,7 +2,7 @@
/* vim: set ts=2: */
var CRC32 = {};
(function(CRC32) {
CRC32.version = '0.1.0';
CRC32.version = '0.2.0';
/* see perf/crc32table.js */
function signed_crc_table() {
var c, table = new Array(256);
@ -20,24 +20,36 @@ function signed_crc_table() {
table[n] = c;
}
return table;
return typeof Int32Array !== 'undefined' ? new Int32Array(table) : table;
}
var table = signed_crc_table();
/* charCodeAt is the best approach for binary strings */
var use_buffer = typeof Buffer !== 'undefined';
function crc32_bstr(bstr) {
for(var crc = -1, i = 0, L=bstr.length-1; i < L;) {
crc = (crc >>> 8) ^ table[(crc ^ bstr.charCodeAt(i++)) & 0xFF];
crc = (crc >>> 8) ^ table[(crc ^ bstr.charCodeAt(i++)) & 0xFF];
if(bstr.length > 32768) if(use_buffer) return crc32_buf(Buffer(bstr));
var crc = -1, L = bstr.length - 1;
for(var i = 0; i < L;) {
crc = table[(crc ^ bstr.charCodeAt(i++)) & 0xFF] ^ (crc >>> 8);
crc = table[(crc ^ bstr.charCodeAt(i++)) & 0xFF] ^ (crc >>> 8);
}
if(i === L) crc = (crc >>> 8) ^ table[(crc ^ bstr.charCodeAt(i++)) & 0xFF];
if(i === L) crc = (crc >>> 8) ^ table[(crc ^ bstr.charCodeAt(i)) & 0xFF];
return crc ^ -1;
}
function crc32_buf(buf) {
for(var crc = -1, i = 0; i != buf.length; ++i) {
crc = (crc >>> 8) ^ table[(crc ^ buf[i]) & 0xFF];
for(var crc = -1, i = 0, L=buf.length-3; i < L;) {
crc = (crc >>> 8) ^ table[(crc^buf[i++])&0xFF];
crc = (crc >>> 8) ^ table[(crc^buf[i++])&0xFF];
crc = (crc >>> 8) ^ table[(crc^buf[i++])&0xFF];
crc = (crc >>> 8) ^ table[(crc^buf[i++])&0xFF];
}
if(i < L+3) {
crc = (crc >>> 8) ^ table[(crc^buf[i++])&0xFF];
if(i < L+3) {
crc = (crc >>> 8) ^ table[(crc^buf[i++])&0xFF];
if(i < L+3) {
crc = (crc >>> 8) ^ table[(crc^buf[i++])&0xFF]; }}}
return crc ^ -1;
}

View File

@ -2,7 +2,7 @@
/* vim: set ts=2: */
var CRC32 = {};
(function(CRC32) {
CRC32.version = '0.1.0';
CRC32.version = '0.2.0';
/* see perf/crc32table.js */
function signed_crc_table() {
var c, table = new Array(256);
@ -20,24 +20,36 @@ function signed_crc_table() {
table[n] = c;
}
return table;
return typeof Int32Array !== 'undefined' ? new Int32Array(table) : table;
}
var table = signed_crc_table();
/* charCodeAt is the best approach for binary strings */
var use_buffer = typeof Buffer !== 'undefined';
function crc32_bstr(bstr) {
for(var crc = -1, i = 0, L=bstr.length-1; i < L;) {
crc = (crc >>> 8) ^ table[(crc ^ bstr.charCodeAt(i++)) & 0xFF];
crc = (crc >>> 8) ^ table[(crc ^ bstr.charCodeAt(i++)) & 0xFF];
if(bstr.length > 32768) if(use_buffer) return crc32_buf(Buffer(bstr));
var crc = -1, L = bstr.length - 1;
for(var i = 0; i < L;) {
crc = table[(crc ^ bstr.charCodeAt(i++)) & 0xFF] ^ (crc >>> 8);
crc = table[(crc ^ bstr.charCodeAt(i++)) & 0xFF] ^ (crc >>> 8);
}
if(i === L) crc = (crc >>> 8) ^ table[(crc ^ bstr.charCodeAt(i++)) & 0xFF];
if(i === L) crc = (crc >>> 8) ^ table[(crc ^ bstr.charCodeAt(i)) & 0xFF];
return crc ^ -1;
}
function crc32_buf(buf) {
for(var crc = -1, i = 0; i != buf.length; ++i) {
crc = (crc >>> 8) ^ table[(crc ^ buf[i]) & 0xFF];
for(var crc = -1, i = 0, L=buf.length-3; i < L;) {
crc = (crc >>> 8) ^ table[(crc^buf[i++])&0xFF];
crc = (crc >>> 8) ^ table[(crc^buf[i++])&0xFF];
crc = (crc >>> 8) ^ table[(crc^buf[i++])&0xFF];
crc = (crc >>> 8) ^ table[(crc^buf[i++])&0xFF];
}
if(i < L+3) {
crc = (crc >>> 8) ^ table[(crc^buf[i++])&0xFF];
if(i < L+3) {
crc = (crc >>> 8) ^ table[(crc^buf[i++])&0xFF];
if(i < L+3) {
crc = (crc >>> 8) ^ table[(crc^buf[i++])&0xFF]; }}}
return crc ^ -1;
}

View File

@ -6,6 +6,98 @@ var bits = [
[ "foo bar baz٪☃🍣", 1531648243]
];
if(typeof module !== "undefined") module.exports = bits;
if(typeof require !== 'undefined') {
var js_crc32 = require('../');
var buffer_crc32 = require('./buffer-crc32');
var crc32 = require('./crc32');
var node_crc = require('./node-crc');
function z1(bstr) { return js_crc32.bstr(bstr); }
function z2(bstr) { return buffer_crc32.signed(bstr); }
function z3(bstr) { return crc32(bstr); }
function z4(bstr) { return node_crc.crc32(bstr);}
function b1(buf) { return js_crc32.buf(buf); }
function b2(buf) { return buffer_crc32.signed(buf); }
function b3(buf) { return crc32(buf); }
function b4(buf) { return node_crc.crc32(buf); }
function u1(str) { return js_crc32.str(str); }
function u2(str) { return buffer_crc32.signed(str); }
var ntests, len_max;
switch(process.env.MODE) {
case "A": ntests = 100000; len_max = 256; break;
case "B": ntests = 10000; len_max = 1024; break;
case "C": ntests = 10000; len_max = 4096; break;
case "D": ntests = 1000; len_max = 16384; break;
case "E": ntests = 1000; len_max = 65536; break;
case "F": ntests = 100; len_max = 262144; break;
default: ntests = 10000; len_max = 1024; break;
}
var btest = true, utest = true;
var bstr_tests = [];
var ustr_tests = [];
var len_min = 1;
var corpus = new Array(0x0800);
for(var k = 0; k < 0x0800; ++k) corpus[k] = String.fromCharCode(k)
len_max --;
k = (Math.random()*0x800)|0;
for(var i = 0; i < ntests; ++i) {
var l = (Math.random() * (len_max - len_min))|0 + len_min;
var s = new Array(l), t = new Array(l);
if(btest) for(var j = 0; j < l; ++j) s[j] = corpus[(i+j+k)&127];
if(utest) for(var j = 0; j < l; ++j) t[j] = corpus[(i+j+k)&0x7FF];
var ss = s.join("");
bstr_tests[i] = [ss, new Buffer(ss)];
ustr_tests[i] = t.join("");
}
var assert = require('assert');
function fix(str) { return parseInt(str, 16)|0; }
if(btest) for(var j = 0; j != ntests; ++j) {
assert.equal(z1(bstr_tests[j][0]), b1(bstr_tests[j][1]));
assert.equal(z1(bstr_tests[j][0]), z2(bstr_tests[j][0]));
assert.equal(z1(bstr_tests[j][0]), fix(z3(bstr_tests[j][0])));
assert.equal(z1(bstr_tests[j][0]), fix(z4(bstr_tests[j][0])));
assert.equal(b1(bstr_tests[j][1]), b2(bstr_tests[j][1]));
assert.equal(b1(bstr_tests[j][1]), fix(b3(bstr_tests[j][1])));
assert.equal(b1(bstr_tests[j][1]), fix(b4(bstr_tests[j][1])));
}
if(utest) for(var j = 0; j != ntests; ++j) {
assert.equal(u1(ustr_tests[j]), u2(ustr_tests[j]));
}
var BM = require('../perf/bm');
var suite = new BM('binary string (' + len_max + ')');
suite.add('js-crc32', function() { for(var j = 0; j != ntests; ++j) z1(bstr_tests[j][0]); });
suite.add('buffer-crc32', function() { for(var j = 0; j != ntests; ++j) z2(bstr_tests[j][0]); });
if(len_max < 4096) {
suite.add('crc32', function() { for(var j = 0; j != ntests; ++j) z3(bstr_tests[j][0]); });
suite.add('node_crc', function() { for(var j = 0; j != ntests; ++j) z4(bstr_tests[j][0]); });
}
suite.run();
suite = new BM('buffer (' + len_max + ')');
suite.add('js-crc32', function() { for(var j = 0; j != ntests; ++j) b1(bstr_tests[j][1]); });
suite.add('buffer-crc32', function() { for(var j = 0; j != ntests; ++j) b2(bstr_tests[j][1]); });
if(len_max < 1024) {
suite.add('crc32', function() { for(var j = 0; j != ntests; ++j) b3(bstr_tests[j][1]); });
suite.add('node_crc', function() { for(var j = 0; j != ntests; ++j) b4(bstr_tests[j][1]); });
}
suite.run();
var suite = new BM('unicode string (' + len_max + ')');
suite.add('js-crc32', function() { for(var j = 0; j != ntests; ++j) u1(ustr_tests[j]); });
suite.add('buffer-crc32', function() { for(var j = 0; j != ntests; ++j) u2(ustr_tests[j]); });
suite.run();
}
crc32table = [
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA,
0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,

92
misc/integration.js Normal file
View File

@ -0,0 +1,92 @@
if(typeof require !== 'undefined') {
var js_crc32 = require('../');
var buffer_crc32 = require('./buffer-crc32');
var crc32 = require('./crc32');
var node_crc = require('./node-crc');
function z1(bstr) { return js_crc32.bstr(bstr); }
function z2(bstr) { return buffer_crc32.signed(bstr); }
function z3(bstr) { return crc32(bstr); }
function z4(bstr) { return node_crc.crc32(bstr);}
function b1(buf) { return js_crc32.buf(buf); }
function b2(buf) { return buffer_crc32.signed(buf); }
function b3(buf) { return crc32(buf); }
function b4(buf) { return node_crc.crc32(buf); }
function u1(str) { return js_crc32.str(str); }
function u2(str) { return buffer_crc32.signed(str); }
var ntests, len_max;
switch(process.env.MODE) {
case "A": ntests = 100000; len_max = 256; break;
case "B": ntests = 10000; len_max = 1024; break;
case "C": ntests = 10000; len_max = 4096; break;
case "D": ntests = 1000; len_max = 16384; break;
case "E": ntests = 1000; len_max = 65536; break;
case "F": ntests = 100; len_max = 262144; break;
default: ntests = 10000; len_max = 1024; break;
}
var btest = true, utest = true;
var bstr_tests = [];
var ustr_tests = [];
var len_min = 1;
var corpus = new Array(0x0800);
for(var k = 0; k < 0x0800; ++k) corpus[k] = String.fromCharCode(k)
len_max --;
k = (Math.random()*0x800)|0;
for(var i = 0; i < ntests; ++i) {
var l = (Math.random() * (len_max - len_min))|0 + len_min;
var s = new Array(l), t = new Array(l);
if(btest) for(var j = 0; j < l; ++j) s[j] = corpus[(i+j+k)&127];
if(utest) for(var j = 0; j < l; ++j) t[j] = corpus[(i+j+k)&0x7FF];
var ss = s.join("");
bstr_tests[i] = [ss, new Buffer(ss)];
ustr_tests[i] = t.join("");
}
var assert = require('assert');
function fix(str) { return parseInt(str, 16)|0; }
if(btest) for(var j = 0; j != ntests; ++j) {
assert.equal(z1(bstr_tests[j][0]), b1(bstr_tests[j][1]));
assert.equal(z1(bstr_tests[j][0]), z2(bstr_tests[j][0]));
assert.equal(z1(bstr_tests[j][0]), fix(z3(bstr_tests[j][0])));
assert.equal(z1(bstr_tests[j][0]), fix(z4(bstr_tests[j][0])));
assert.equal(b1(bstr_tests[j][1]), b2(bstr_tests[j][1]));
assert.equal(b1(bstr_tests[j][1]), fix(b3(bstr_tests[j][1])));
assert.equal(b1(bstr_tests[j][1]), fix(b4(bstr_tests[j][1])));
}
if(utest) for(var j = 0; j != ntests; ++j) {
assert.equal(u1(ustr_tests[j]), u2(ustr_tests[j]));
}
var BM = require('../perf/bm');
var suite = new BM('binary string (' + len_max + ')');
suite.add('js-crc32', function() { for(var j = 0; j != ntests; ++j) z1(bstr_tests[j][0]); });
suite.add('buffer-crc32', function() { for(var j = 0; j != ntests; ++j) z2(bstr_tests[j][0]); });
if(len_max < 4096) {
suite.add('crc32', function() { for(var j = 0; j != ntests; ++j) z3(bstr_tests[j][0]); });
suite.add('node_crc', function() { for(var j = 0; j != ntests; ++j) z4(bstr_tests[j][0]); });
}
suite.run();
suite = new BM('buffer (' + len_max + ')');
suite.add('js-crc32', function() { for(var j = 0; j != ntests; ++j) b1(bstr_tests[j][1]); });
suite.add('buffer-crc32', function() { for(var j = 0; j != ntests; ++j) b2(bstr_tests[j][1]); });
if(len_max < 1024) {
suite.add('crc32', function() { for(var j = 0; j != ntests; ++j) b3(bstr_tests[j][1]); });
suite.add('node_crc', function() { for(var j = 0; j != ntests; ++j) b4(bstr_tests[j][1]); });
}
suite.run();
var suite = new BM('unicode string (' + len_max + ')');
suite.add('js-crc32', function() { for(var j = 0; j != ntests; ++j) u1(ustr_tests[j]); });
suite.add('buffer-crc32', function() { for(var j = 0; j != ntests; ++j) u2(ustr_tests[j]); });
suite.run();
}

18
misc/perf.sh Executable file
View File

@ -0,0 +1,18 @@
#!/bin/bash
cd misc &>/dev/null
git_module() {
if [ ! -e "$1/" ]; then git clone $2; fi
cd "$1"
git pull
cd - &>/dev/null
}
echo "::: downloading modules"
git_module node-crc https://github.com/alexgorbatchev/node-crc 2>/dev/null # crc
git_module crc32 https://github.com/beatgammit/crc32 2>/dev/null # crc32
git_module buffer-crc32 https://github.com/brianloveswords/buffer-crc32 2>/dev/null # buffer-crc32
for i in A B C D E F; do MODE="$i" node integration.js; done
# for i in E F; do MODE="$i" node integration.js; done

View File

@ -1,6 +1,6 @@
{
"name": "crc-32",
"version": "0.1.0",
"version": "0.2.0",
"author": "sheetjs",
"description": "Pure-JS CRC-32",
"keywords": [ "crc32", "checksum", "crc" ],

82
perf.txt Normal file
View File

@ -0,0 +1,82 @@
--- binary string (255) ---
✓ js-crc32 x 25.41 ops/sec ±0.40% (45 runs sampled)
✓ buffer-crc32 x 6.63 ops/sec ±1.09% (20 runs sampled)
✓ crc32 x 0.84 ops/sec ±1.17% (6 runs sampled)
✓ node_crc x 1.99 ops/sec ±0.59% (8 runs sampled)
Fastest is js-crc32
--- buffer (255) ---
✓ js-crc32 x 30.68 ops/sec ±0.40% (55 runs sampled)
✓ buffer-crc32 x 26.15 ops/sec ±0.36% (47 runs sampled)
✓ crc32 x 7.58 ops/sec ±0.61% (22 runs sampled)
✓ node_crc x 3.51 ops/sec ±0.56% (12 runs sampled)
Fastest is js-crc32
--- unicode string (255) ---
✓ js-crc32 x 11.65 ops/sec ±0.35% (32 runs sampled)
✓ buffer-crc32 x 3.17 ops/sec ±4.01% (12 runs sampled)
Fastest is js-crc32
--- binary string (1023) ---
✓ js-crc32 x 65.10 ops/sec ±0.36% (68 runs sampled)
✓ buffer-crc32 x 25.63 ops/sec ±3.38% (60 runs sampled)
✓ crc32 x 2.52 ops/sec ±0.65% (10 runs sampled)
✓ node_crc x 9.41 ops/sec ±0.55% (27 runs sampled)
Fastest is js-crc32
--- buffer (1023) ---
✓ js-crc32 x 78.58 ops/sec ±0.42% (82 runs sampled)
✓ buffer-crc32 x 73.15 ops/sec ±0.41% (76 runs sampled)
✓ crc32 x 21.28 ops/sec ±0.37% (39 runs sampled)
✓ node_crc x 12.46 ops/sec ±0.42% (34 runs sampled)
Fastest is js-crc32
--- unicode string (1023) ---
✓ js-crc32 x 29.07 ops/sec ±0.51% (51 runs sampled)
✓ buffer-crc32 x 9.25 ops/sec ±6.61% (35 runs sampled)
Fastest is js-crc32
--- binary string (4095) ---
✓ js-crc32 x 16.57 ops/sec ±0.47% (44 runs sampled)
✓ buffer-crc32 x 13.30 ops/sec ±8.05% (34 runs sampled)
✓ crc32 x 0.60 ops/sec ±0.46% (5 runs sampled)
✓ node_crc x 3.01 ops/sec ±0.52% (11 runs sampled)
Fastest is js-crc32
--- buffer (4095) ---
✓ js-crc32 x 20.35 ops/sec ±0.45% (37 runs sampled)
✓ buffer-crc32 x 18.86 ops/sec ±0.45% (50 runs sampled)
Fastest is js-crc32
--- unicode string (4095) ---
✓ js-crc32 x 7.20 ops/sec ±0.96% (22 runs sampled)
✓ buffer-crc32 x 4.20 ops/sec ±5.73% (13 runs sampled)
Fastest is js-crc32
--- binary string (16383) ---
✓ js-crc32 x 41.40 ops/sec ±0.15% (55 runs sampled)
✓ buffer-crc32 x 39.18 ops/sec ±5.86% (63 runs sampled)
Fastest is js-crc32
--- buffer (16383) ---
✓ js-crc32 x 51.36 ops/sec ±0.34% (67 runs sampled)
✓ buffer-crc32 x 47.72 ops/sec ±0.31% (63 runs sampled)
Fastest is js-crc32
--- unicode string (16383) ---
✓ js-crc32 x 18.47 ops/sec ±0.74% (49 runs sampled)
✓ buffer-crc32 x 11.29 ops/sec ±3.94% (31 runs sampled)
Fastest is js-crc32
--- binary string (65535) ---
✓ js-crc32 x 10.14 ops/sec ±4.43% (28 runs sampled)
✓ buffer-crc32 x 7.29 ops/sec ±5.49% (27 runs sampled)
Fastest is js-crc32
--- buffer (65535) ---
✓ js-crc32 x 12.34 ops/sec ±0.26% (34 runs sampled)
✓ buffer-crc32 x 11.29 ops/sec ±0.56% (32 runs sampled)
Fastest is js-crc32
--- unicode string (65535) ---
✓ js-crc32 x 4.28 ops/sec ±1.17% (14 runs sampled)
✓ buffer-crc32 x 2.73 ops/sec ±3.73% (10 runs sampled)
Fastest is js-crc32
--- binary string (262143) ---
✓ js-crc32 x 21.41 ops/sec ±3.43% (43 runs sampled)
✓ buffer-crc32 x 23.56 ops/sec ±4.02% (41 runs sampled)
Fastest is js-crc32
--- buffer (262143) ---
✓ js-crc32 x 31.51 ops/sec ±0.49% (55 runs sampled)
✓ buffer-crc32 x 27.56 ops/sec ±0.60% (51 runs sampled)
Fastest is js-crc32
--- unicode string (262143) ---
✓ js-crc32 x 11.00 ops/sec ±0.98% (31 runs sampled)
✓ buffer-crc32 x 6.70 ops/sec ±2.99% (19 runs sampled)
Fastest is js-crc32

View File

@ -1,4 +1,4 @@
/* ssf.js (C) 2014 SheetJS -- http://sheetjs.com */
/* bm.js (C) 2014 SheetJS -- http://sheetjs.com */
var Benchmark = require('benchmark');
var c = require('ansi')(process.stdout);
@ -12,17 +12,24 @@ function BM(name) {
if(!(this instanceof BM)) return new BM(name);
console.log("--- " + name + " ---");
this.suite = new Benchmark.Suite(name, { onComplete: suite_end });
this.suites = [];
this.maxlen = 0;
}
BM.prototype.run = function() { this.suite.run(); };
BM.prototype.run = function() {
var maxlen = this.maxlen, ss = this.suite;
this.suites.forEach(function(s) { ss.add(s[0] + new Array(maxlen-s[0].length+1).join(" "), s[1]); });
if(this.suites.length > 0) this.suite.run();
};
BM.prototype.add = function(msg, test) {
this.suite.add(msg, {
this.suites.push([msg, {
onCycle: test_cycle,
onComplete: test_end,
defer: false,
fn: test
});
}]);
this.maxlen = Math.max(this.maxlen, msg.length);
};
module.exports = BM;