Skip to content

Commit

Permalink
2x faster
Browse files Browse the repository at this point in the history
  • Loading branch information
andrasq committed Sep 19, 2020
1 parent a38ac7a commit a25873d
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 53 deletions.
42 changes: 25 additions & 17 deletions README.md
Expand Up @@ -10,27 +10,34 @@ My try at nudging the state of the art past the 1 million UUIDs per second barri
RFC at https://tools.ietf.org/html/rfc4122


Summary
-------
Overview
--------

const uuid = require('uuid-quick');
let id = uuid();

`uuid-quick` is a fast Version 4 (random) UUID generator (see RFC Section 4.4, page 14).
The new version 0.2.0 is almost twice as version 0.1.0 was.

$ node benchmark.js

qtimeit=0.21.0 node=10.15.0 v8=6.8.275.32-node.45 platform=linux kernel=4.9.0-0.bpo.4-amd64 up_threshold=false
arch=ia32 mhz=4183 cpuCount=8 cpu="Intel(R) Core(TM) i7-6700K CPU @ 4.00GHz"
name speed rate
uuid 423,029 ops/sec 212
node-uuid 417,133 ops/sec 209
fast-uuid 766,254 ops/sec 383 >
uuid-quick 2,229,696 ops/sec 1115 >>
mongoid-js short 34,198,722 ops/sec 17099 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
qtimeit=0.21.0 node=10.15.0 v8=6.8.275.32-node.45 platform=linux kernel=5.5.0-1-amd64 up_threshold=false
arch=ia32 mhz=4482[os] cpuCount=16 cpu="AMD Ryzen 7 3800X 8-Core Processor"
name speed rate
uuid 277,767 ops/sec 1000 >>
node-uuid 304,581 ops/sec 1097 >>
fast-uuid 871,343 ops/sec 3137 >>>>>>
uuid-quick 4,892,916 ops/sec 17615 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

The results look pretty good, 3-5x faster than the more common uuid packages. Still much much
slower than sequential id generators like `mongoid-js`. This latter 15x speed difference is
inherent in the nature of these UUIDS, they have to be random in every position each time;
sequential generators have redundancies that can be optimized out.
If only uniqueness is needed, then a good sequential id generator like e.g. `mongoid-js` can be
much faster than even the fastest uuid. This speed difference is inherent in UUID's, they have to be
random at avery position; sequential generators have redundancies that can be optimized out.

qtimeit=0.21.0 node=10.15.0 v8=6.8.275.32-node.45 platform=linux kernel=5.5.0-1-amd64 up_threshold=false
arch=ia32 mhz=4480[os] cpuCount=16 cpu="AMD Ryzen 7 3800X 8-Core Processor"
name speed rate
uuid-quick 4,961,158 ops/sec 1000 >>
mongoid-js short 34,117,176 ops/sec 6877 >>>>>>>>>>>>>>


API
Expand All @@ -41,8 +48,8 @@ API
The `uuid-quick` package exports a function that returns Version 4 UUID strings. These are 32
random hexadecimal characters separated by 4 dashes `-` grouped 8-4-4-4-12 in the form
"11111111-1111-4111-8111-111111111111", where "1" is a hex digit, "4" is the letter '4', and "8"
is one of '8', '9', 'a' or 'b'. (I said 32, but careful readers will count only 30.5 random hex
characters. I, umm, approximated.)
is one of '8', '9', 'a' or 'b'. (I said 32, but careful readers will note that there are
actually only 30.5 random hex characters, the other 6 bits of the 32 chars are constant.)

const uuid = require('uuid-quick');
let id = uuid();
Expand All @@ -58,12 +65,13 @@ same as `uuid()`
### uuid.rand

the random number generator function to use. By default this property is set to `Math.random`.
Must return floating-point values between 0 and 1 with at least 36 bits of precision.
Must return floating-point values between 0 and 1 with at least 48 bits of precision.


Changelog
---------

- 0.2.0 - rewrite, now 2x faster
- 0.1.0 - first version


Expand Down
8 changes: 7 additions & 1 deletion benchmark.js
Expand Up @@ -46,7 +46,7 @@ var mongoidFactory = new mongoidjs.MongoId();
qtimeit.bench.timeGoal = .2;
qtimeit.bench.showRunDetails = false;
qtimeit.bench.visualize = true;
qtimeit.bench.baselineAvg = 2000000;
//qtimeit.bench.baselineAvg = 2000000;
qtimeit.bench.bargraphScale = 2;
qtimeit.bench.opsPerTest = 2;

Expand All @@ -64,5 +64,11 @@ qtimeit.bench({
//'hyperid': function() { x = hyperid(); x2 = hyperid() }, // not a uuid, is fixed uuid+counter
//'mongoid-js': function() { x = mongoidjs(); x2 = mongoidjs() },
//'mongoid-js': function() { x = mongoidFactory.fetch(); x2 = mongoidFactory.fetch() },
//'mongoid-js short': function() { x = mongoidFactory.fetchShort(); x2 = mongoidFactory.fetchShort() },
});

qtimeit.bench({
'uuid-quick': function() { x = uuidquick(); x2 = uuidquick() },
//'mongoid-js': function() { x = mongoidFactory.fetch(); x2 = mongoidFactory.fetch() },
'mongoid-js short': function() { x = mongoidFactory.fetchShort(); x2 = mongoidFactory.fetchShort() },
});
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "uuid-quick",
"version": "0.1.1",
"version": "0.2.0",
"description": "quicker uuid, very fast",

"main": "uuid.js",
Expand Down
66 changes: 32 additions & 34 deletions uuid.js
@@ -1,7 +1,7 @@
/**
* uuid-quick -- much faster uuid() generator
*
* Copyright (C) 2019 Andras Radics
* Copyright (C) 2019-2020 Andras Radics
* Licensed under the Apache License, Version 2.0
*
* 2019-01-15 - AR.
Expand All @@ -19,50 +19,48 @@ uuid.v4 = uuid_4;
uuid.rand = Math.random;
toStruct(uuid);

var CH_1 = 0x31;
var CH_4 = 0x34;
var CH_8 = 0x38;
var CH_DASH = 0x2D;

var hexchars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
for (var i=0; i<16; i++) hexchars[i * 16] = hexchars[i]; // map high nybble too
// fromCharCode is with many args is faster but only since node-v0.11; spread args faster since node-v8
// In newer node versions fromCharCode.apply is also fast, but spread args are faster.
// fromCharCode.apply is only fast since node-v8, spread args are faster but were 30x slower before v8
// Node before v0.11 is slow with multi-arg fromCharCode and is faster with a convert-char-at-a-time loop.
function tryEval(s) { try { return eval(s) } catch (e) {} }
var fromCharCodeLoop = eval("true && function(a) { var s = ''; for (var i=0; i<a.length; i++) s += String.fromCharCode(a[i]); return s }");
var fromCharCodeSpread = tryEval("true && function(a) { return String.fromCharCode(...a) }");
var fromCharCode = eval("parseInt(process.versions.node) >= 9 ? fromCharCodeSpread : fromCharCodeLoop");

// template from fast-uuid.js: "10000000-1000-4000-8000-100000000000";
var hexmap = [0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66];
var arr = new Array(36);
function uuid_4() {
var rand = uuid.rand;

setChars(arr, rand(), 0);
setChars(arr, rand(), 9);
setChars(arr, rand(), 18);
setChars(arr, rand(), 27);
setChars12(arr, rand(), 0);
setChars12(arr, rand(), 12);
setChars12(arr, rand(), 24);

arr[8] = '-';
arr[13] = '-';
arr[14] = '4';
arr[18] = '-';
arr[19] = hexchars[8 + (arr[19].charCodeAt(0) & 3)];
arr[23] = '-';
arr[8] = CH_DASH;
arr[13] = CH_DASH;
arr[14] = CH_4;
arr[18] = CH_DASH
arr[19] = CH_8 + (arr[19] & 3);
arr[23] = CH_DASH;

//return arr.join(''); // 1.15m/s v8, 1.2m/s v10
return arrayConcat(arr); // 1.9m/s v8, 2.1m/s v10
return fromCharCode(arr);
}

function arrayConcat(a) {
var s = a[0], len = a.length;
for (var i=1; i<len; i++) s += a[i];
return s;
}

// Set 9 chars in buf starting at offset pos from the value n.
// N must be a float between 0 and 1 with at least 36 bits of precision.
// note: node-v8 is very slow to copy into a Buffer, node-v10 is very fast
// Arrays are a good compromise, fast enough on all versions.
// Using a hexmap object is half the speed, and so is a long array.
function setChars( buf, n, pos ) {
for (var i=0; i<8; i+=2) {
n *= 0x100;
buf[pos+i+0] = hexchars[n & 0xF0];
buf[pos+i+1] = hexchars[n & 0x0F];
n -= Math.floor(n);
// extract 12 4-bit values into buf from the value n, to offets [pos..pos+11]
// n must be a random float between 0 and 1 with at least 48 bits of precision.
function setChars12( buf, n, pos ) {
for (var i=0; i<12; i+=3) {
n *= 0x1000;
buf[pos + i + 0] = hexmap[(n ) & 0xF];
buf[pos + i + 1] = hexmap[(n >>> 4) & 0xF];
buf[pos + i + 2] = hexmap[(n >>> 8) & 0xF];
}
buf[pos+8] = hexchars[(n * 16) & 0xF];
return;
}

Expand Down

0 comments on commit a25873d

Please sign in to comment.