diff --git a/Timestamp48/benchmark.js b/Timestamp48/benchmark.js new file mode 100644 index 0000000..4377d8d --- /dev/null +++ b/Timestamp48/benchmark.js @@ -0,0 +1,67 @@ +'use strict'; + +const original = require('./gpt-5.js'); +const optimized = require('./optimized.js'); + +const benchmark = (name, fn, iterations) => { + // Warmup + for (let i = 0; i < 1000; i++) fn(); + + // Actual benchmark + const start = process.hrtime.bigint(); + for (let i = 0; i < iterations; i++) { + fn(); + } + const end = process.hrtime.bigint(); + + const timeMs = Number(end - start) / 1e6; + const opsPerSec = (iterations / timeMs) * 1000; + + console.log(`${name}:`); + console.log(` Time: ${timeMs.toFixed(2)}ms`); + console.log(` Ops/sec: ${opsPerSec.toFixed(0)}`); + const avgTime = ((timeMs / iterations) * 1000).toFixed(3); + console.log(` Avg time per op: ${avgTime}μs`); + + return { timeMs, opsPerSec }; +}; + +const iterations = 100000; + +console.log(`\nBenchmark: ${iterations} UUID generations\n`); +console.log('='.repeat(50)); + +const originalResult = benchmark( + 'Original (gpt-5.js)', + () => original.generateV7Base64Url(), + iterations, +); + +console.log(''); + +const optimizedResult = benchmark( + 'Optimized', + () => optimized.generateV7Base64Url(), + iterations, +); + +console.log(''); + +const batchResult = benchmark( + 'Batch (10 at once)', + () => optimized.generateBatch(10), + iterations / 10, +); + +console.log('\n' + '='.repeat(50)); +console.log('\nSummary:'); +const perfGain = ( + (optimizedResult.opsPerSec / originalResult.opsPerSec - 1) * + 100 +).toFixed(1); +console.log(` Performance gain: ${perfGain}%`); +const batchEff = ( + ((batchResult.opsPerSec * 10) / optimizedResult.opsPerSec - 1) * + 100 +).toFixed(1); +console.log(` Batch efficiency: ${batchEff}% faster per UUID`); diff --git a/Timestamp48/optimized.js b/Timestamp48/optimized.js new file mode 100644 index 0000000..aa709e5 --- /dev/null +++ b/Timestamp48/optimized.js @@ -0,0 +1,97 @@ +'use strict'; + +const crypto = require('crypto'); + +// Constants for better readability +const VERSION_7 = 0x70; +const VARIANT_RFC4122 = 0x80; +const VARIANT_MASK = 0x3f; +const SEQ_MAX = 0x0fff; + +// Pre-allocated buffers for better performance +const sharedBuffer = Buffer.allocUnsafe(16); +const randomPool = Buffer.allocUnsafe(1024); +let poolOffset = 1024; + +// State management +const state = { + lastTimestamp: 0n, + sequence: 0, +}; + +// Get random bytes from pre-allocated pool +const getRandomBytes = (length) => { + if (poolOffset + length > randomPool.length) { + crypto.randomFillSync(randomPool); + poolOffset = 0; + } + const result = randomPool.subarray(poolOffset, poolOffset + length); + poolOffset += length; + return result; +}; + +// Get current timestamp in milliseconds +const getCurrentTimestamp = () => BigInt(Date.now()); + +// Pack UUID v7 components into buffer +const packUuidV7 = (timestamp, sequence, randomTail) => { + // Write 48-bit timestamp (big-endian) + sharedBuffer.writeBigUInt64BE(timestamp << 16n, 0); + + // Version (7) and sequence high bits + sharedBuffer[6] = VERSION_7 | ((sequence >> 8) & 0x0f); + sharedBuffer[7] = sequence & 0xff; + + // RFC 4122 variant and random tail + randomTail.copy(sharedBuffer, 8); + sharedBuffer[8] = (sharedBuffer[8] & VARIANT_MASK) | VARIANT_RFC4122; + + return sharedBuffer; +}; + +/** + * Generates a UUIDv7-like identifier with improved performance + * @returns {string} Base64URL encoded 22-character string + */ +const generateV7Base64Url = () => { + let timestamp = getCurrentTimestamp(); + + // Handle sequence for monotonicity and counter overflow + if (timestamp <= state.lastTimestamp) { + timestamp = state.lastTimestamp; + state.sequence = (state.sequence + 1) & SEQ_MAX; + if (state.sequence === 0) { + // Sequence overflow, increment timestamp. + // This is a robust way to handle high generation rates + // without busy-waiting for the next millisecond. + timestamp++; + state.lastTimestamp = timestamp; + } + } else { + // New millisecond or time moved backwards: randomize sequence + state.lastTimestamp = timestamp; + // Using random bytes is faster than crypto.randomInt() + state.sequence = getRandomBytes(2).readUInt16BE(0) & SEQ_MAX; + } + + // Get random tail from pool + const randomTail = getRandomBytes(8); + + // Pack and encode + const packed = packUuidV7(timestamp, state.sequence, randomTail); + return packed.toString('base64url'); +}; + +// Batch generation for better performance +const generateBatch = (count) => { + const results = new Array(count); + for (let i = 0; i < count; i++) { + results[i] = generateV7Base64Url(); + } + return results; +}; + +module.exports = { + generateV7Base64Url, + generateBatch, +}; diff --git a/Timestamp48/optimized.mjs b/Timestamp48/optimized.mjs new file mode 100644 index 0000000..842e5a0 --- /dev/null +++ b/Timestamp48/optimized.mjs @@ -0,0 +1,130 @@ +// optimized.mjs - ESM browser-safe version with performance improvements + +// Constants +const VERSION_7 = 0x70; +const VARIANT_RFC4122 = 0x80; +const VARIANT_MASK = 0x3f; +const SEQ_MAX = 0x0fff; +const BASE64_CHARS = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'; + +// Pre-allocated typed arrays for better performance +const sharedBytes = new Uint8Array(16); +const randomPool = new Uint8Array(1024); +let poolOffset = 1024; + +// State management +const state = { + lastTimestamp: 0n, + sequence: 0, +}; + +// Optimized Base64URL encoding +const encodeBase64Url = (bytes) => { + let result = ''; + let i = 0; + + // Process 3 bytes at a time + while (i < 15) { + const b1 = bytes[i++]; + const b2 = bytes[i++]; + const b3 = bytes[i++]; + + result += BASE64_CHARS[b1 >> 2]; + result += BASE64_CHARS[((b1 & 3) << 4) | (b2 >> 4)]; + result += BASE64_CHARS[((b2 & 15) << 2) | (b3 >> 6)]; + result += BASE64_CHARS[b3 & 63]; + } + + // Handle last byte + const lastByte = bytes[15]; + result += BASE64_CHARS[lastByte >> 2]; + result += BASE64_CHARS[(lastByte & 3) << 4]; + + return result; +}; + +// Get random bytes from pre-allocated pool +const getRandomBytes = (length) => { + if (poolOffset + length > randomPool.length) { + globalThis.crypto.getRandomValues(randomPool); + poolOffset = 0; + } + const result = randomPool.subarray(poolOffset, poolOffset + length); + poolOffset += length; + return result; +}; + +// Get random 12-bit sequence +const getRandomSequence = () => { + const bytes = getRandomBytes(2); + return ((bytes[0] << 8) | bytes[1]) & SEQ_MAX; +}; + +// Get current timestamp +const getCurrentTimestamp = () => BigInt(Date.now()); + +// Pack UUID v7 components +const packUuidV7 = (timestamp, sequence, randomTail) => { + // Write 48-bit timestamp (big-endian) + sharedBytes[0] = Number((timestamp >> 40n) & 0xffn); + sharedBytes[1] = Number((timestamp >> 32n) & 0xffn); + sharedBytes[2] = Number((timestamp >> 24n) & 0xffn); + sharedBytes[3] = Number((timestamp >> 16n) & 0xffn); + sharedBytes[4] = Number((timestamp >> 8n) & 0xffn); + sharedBytes[5] = Number(timestamp & 0xffn); + + // Version and sequence + sharedBytes[6] = VERSION_7 | ((sequence >> 8) & 0x0f); + sharedBytes[7] = sequence & 0xff; + + // Copy random tail and apply variant + sharedBytes.set(randomTail, 8); + sharedBytes[8] = (sharedBytes[8] & VARIANT_MASK) | VARIANT_RFC4122; + + return sharedBytes; +}; + +/** + * Generates a UUIDv7-like identifier optimized for browser performance + * @returns {string} Base64URL encoded 22-character string + */ +export const generateV7Base64Url = () => { + const now = getCurrentTimestamp(); + let timestamp = now > state.lastTimestamp ? now : state.lastTimestamp; + + // Handle sequence for monotonicity + if (timestamp === state.lastTimestamp) { + state.sequence = (state.sequence + 1) & SEQ_MAX; + if (state.sequence === 0) { + // Virtual timestamp increment to avoid blocking + timestamp = state.lastTimestamp + 1n; + } + } else { + state.sequence = getRandomSequence(); + } + + state.lastTimestamp = timestamp; + + // Get random tail from pool + const randomTail = getRandomBytes(8); + + // Pack and encode + packUuidV7(timestamp, state.sequence, randomTail); + return encodeBase64Url(sharedBytes); +}; + +/** + * Generate batch of UUIDs for better performance + * @param {number} count - Number of UUIDs to generate + * @returns {Array} Array of Base64URL encoded UUIDs + */ +export const generateBatch = (count) => { + const results = new Array(count); + for (let i = 0; i < count; i++) { + results[i] = generateV7Base64Url(); + } + return results; +}; + +export default generateV7Base64Url; diff --git a/Timestamp48/test.js b/Timestamp48/test.js new file mode 100644 index 0000000..cf75f13 --- /dev/null +++ b/Timestamp48/test.js @@ -0,0 +1,80 @@ +'use strict'; + +const { test } = require('node:test'); +const assert = require('node:assert'); +const original = require('./gpt-5.js'); +const optimized = require('./optimized.js'); + +test('UUID format validation', () => { + const uuid1 = original.generateV7Base64Url(); + const uuid2 = optimized.generateV7Base64Url(); + + // Check length (22 chars for Base64URL without padding) + assert.strictEqual(uuid1.length, 22); + assert.strictEqual(uuid2.length, 22); + + // Check Base64URL characters + const base64UrlPattern = /^[A-Za-z0-9\-_]+$/; + assert.match(uuid1, base64UrlPattern); + assert.match(uuid2, base64UrlPattern); +}); + +test('Monotonicity within same millisecond', () => { + const uuids = []; + for (let i = 0; i < 100; i++) { + uuids.push(optimized.generateV7Base64Url()); + } + + // Check that UUIDs are unique + const uniqueUuids = new Set(uuids); + assert.strictEqual(uniqueUuids.size, uuids.length); +}); + +test('Batch generation', () => { + const batch = optimized.generateBatch(10); + + assert.strictEqual(batch.length, 10); + + // Check all are unique + const uniqueBatch = new Set(batch); + assert.strictEqual(uniqueBatch.size, batch.length); + + // Check format + batch.forEach((uuid) => { + assert.strictEqual(uuid.length, 22); + assert.match(uuid, /^[A-Za-z0-9\-_]+$/); + }); +}); + +test('Performance comparison', () => { + const iterations = 10000; + + // Test original version + const startOriginal = process.hrtime.bigint(); + for (let i = 0; i < iterations; i++) { + original.generateV7Base64Url(); + } + const endOriginal = process.hrtime.bigint(); + // Convert to ms + const timeOriginal = Number(endOriginal - startOriginal) / 1e6; + + // Test optimized version + const startOptimized = process.hrtime.bigint(); + for (let i = 0; i < iterations; i++) { + optimized.generateV7Base64Url(); + } + const endOptimized = process.hrtime.bigint(); + const timeOptimized = Number(endOptimized - startOptimized) / 1e6; + + console.log(`\nPerformance Results (${iterations} iterations):`); + console.log(`Original: ${timeOriginal.toFixed(2)}ms`); + console.log(`Optimized: ${timeOptimized.toFixed(2)}ms`); + const improvement = ((1 - timeOptimized / timeOriginal) * 100).toFixed(1); + console.log(`Improvement: ${improvement}%`); + + // Optimized should be at least as fast + assert.ok( + timeOptimized <= timeOriginal * 1.1, + 'Optimized version should not be slower', + ); +});