Files
salvium-rs/test/benchmark-batch.js
T
Matt Hess 714721874e Add RandomX proof-of-work implementation with WASM acceleration
- Full RandomX implementation (light mode + full mode)
   - WASM-accelerated Argon2d cache init (37x faster than pure JS)
   - WASM-accelerated SuperscalarHash with SIMD support
   - Parallel dataset generation using worker threads (8 workers)
   - Light mode: ~4s init, Full mode: ~8min init
   - Mining utilities (difficulty calculation, block construction)
   - Progress callbacks for long-running operations

   Performance targets 2023+ platforms (WASM SIMD: Chrome 91+,
   Firefox 89+, Safari 16.4+, Node 16.4+)
2026-01-15 19:19:27 +00:00

180 lines
6.3 KiB
JavaScript

#!/usr/bin/env node
/**
* Benchmark: Batch Dataset Generation
*
* Tests the optimized batch WASM function that generates
* multiple items per call.
*
* Usage:
* source ~/.bash_profile && bun test/benchmark-batch.js
*/
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { initCache as initCacheWasm } from '../src/randomx/argon2d-wasm.js';
import { Blake2Generator, generateSuperscalar, reciprocal, SuperscalarInstructionType } from '../src/randomx/superscalar.js';
import { RANDOMX_CACHE_ACCESSES, RANDOMX_DATASET_ITEM_COUNT } from '../src/randomx/config.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
console.log('Batch Dataset Generation Benchmark');
console.log('===================================\n');
const testKey = new TextEncoder().encode('benchmark key');
// Progress helper
const progress = (percent, msg) => {
const bar = '█'.repeat(Math.floor(percent / 5)) + '░'.repeat(20 - Math.floor(percent / 5));
process.stdout.write(`\r[${bar}] ${percent}% ${msg}`.padEnd(70));
};
// ========== Initialize Cache ==========
console.log('Initializing cache with WASM...');
const cacheStart = Date.now();
const cacheQwords = await initCacheWasm(testKey, (completed, total, pass, slice) => {
const percent = Math.round((completed / total) * 100);
progress(percent, `pass ${pass + 1}/3, slice ${slice + 1}/4`);
});
const cacheMemory = new Uint8Array(cacheQwords.length * 8);
for (let i = 0; i < cacheQwords.length; i++) {
const v = cacheQwords[i];
const pos = i * 8;
cacheMemory[pos] = Number(v & 0xffn);
cacheMemory[pos + 1] = Number((v >> 8n) & 0xffn);
cacheMemory[pos + 2] = Number((v >> 16n) & 0xffn);
cacheMemory[pos + 3] = Number((v >> 24n) & 0xffn);
cacheMemory[pos + 4] = Number((v >> 32n) & 0xffn);
cacheMemory[pos + 5] = Number((v >> 40n) & 0xffn);
cacheMemory[pos + 6] = Number((v >> 48n) & 0xffn);
cacheMemory[pos + 7] = Number((v >> 56n) & 0xffn);
}
process.stdout.write('\r' + ' '.repeat(70) + '\r');
console.log(`Cache initialized in ${((Date.now() - cacheStart) / 1000).toFixed(1)}s\n`);
// Generate programs
console.log('Generating superscalar programs...');
const programs = [];
const reciprocalCache = [];
const gen = new Blake2Generator(testKey);
for (let i = 0; i < RANDOMX_CACHE_ACCESSES; i++) {
const prog = generateSuperscalar(gen);
for (const instr of prog.instructions) {
if (instr.opcode === SuperscalarInstructionType.IMUL_RCP) {
const rcp = reciprocal(instr.imm32);
instr.imm32 = reciprocalCache.length;
reciprocalCache.push(rcp);
}
}
programs.push(prog);
}
console.log(`Generated ${RANDOMX_CACHE_ACCESSES} programs\n`);
// ========== Load WASM ==========
console.log('Loading WASM module...');
const wasmPath = join(__dirname, '../build/randomx.wasm');
const wasmBuffer = readFileSync(wasmPath);
const wasmMemory = new WebAssembly.Memory({
initial: 4096 + 1024, // 256MB + 64MB working space
maximum: 8192
});
const imports = {
env: {
memory: wasmMemory,
abort: () => {}
}
};
const wasmModule = await WebAssembly.instantiate(wasmBuffer, imports);
const wasm = wasmModule.instance.exports;
const mem = new Uint8Array(wasmMemory.buffer);
// Copy cache
mem.set(cacheMemory, 0);
wasm.superscalar_init(0, cacheMemory.length / 64);
// Set up programs
const programsPtr = cacheMemory.length + 4096;
const reciprocalsWasmPtr = programsPtr + 8 * 512 * 8;
const reciprocalsBytes = new BigUint64Array(reciprocalCache.length);
for (let i = 0; i < reciprocalCache.length; i++) {
reciprocalsBytes[i] = reciprocalCache[i];
}
mem.set(new Uint8Array(reciprocalsBytes.buffer), reciprocalsWasmPtr);
wasm.setup_programs(programsPtr, reciprocalsWasmPtr, reciprocalCache.length);
let instrOffset = 0;
for (let p = 0; p < programs.length; p++) {
const prog = programs[p];
const startOffset = instrOffset;
for (const instr of prog.instructions) {
const ptr = programsPtr + instrOffset;
mem[ptr] = instr.opcode;
mem[ptr + 1] = instr.dst;
mem[ptr + 2] = instr.src;
mem[ptr + 3] = instr.mod;
mem[ptr + 4] = instr.imm32 & 0xff;
mem[ptr + 5] = (instr.imm32 >> 8) & 0xff;
mem[ptr + 6] = (instr.imm32 >> 16) & 0xff;
mem[ptr + 7] = (instr.imm32 >> 24) & 0xff;
instrOffset += 8;
}
wasm.set_program_meta(p, startOffset, prog.instructions.length, prog.addressRegister);
}
console.log('WASM ready\n');
// ========== Benchmark ==========
const batchSizes = [100, 1000, 10000];
const totalTestItems = 100000; // 100K items for benchmark
// Output buffer after programs and reciprocals (need space for largest batch)
const maxBatchSize = 10000;
const outputPtr = reciprocalsWasmPtr + reciprocalCache.length * 8 + 4096;
console.log('Batch size comparison:');
console.log('----------------------');
for (const batchSize of batchSizes) {
const batches = Math.ceil(totalTestItems / batchSize);
const start = Date.now();
for (let b = 0; b < batches; b++) {
const startItem = b * batchSize;
const count = Math.min(batchSize, totalTestItems - startItem);
wasm.init_dataset_batch(BigInt(startItem), count, outputPtr, RANDOMX_CACHE_ACCESSES);
}
const elapsed = (Date.now() - start) / 1000;
const itemsPerSec = Math.round(totalTestItems / elapsed);
console.log(`Batch size ${batchSize.toString().padStart(5)}: ${itemsPerSec.toLocaleString().padStart(10)} items/s`);
}
// Full dataset estimate with best batch size
console.log('\nFull Dataset Estimate (34M items):');
const finalBatchSize = 10000;
const testStart = Date.now();
const testBatches = 100;
for (let b = 0; b < testBatches; b++) {
wasm.init_dataset_batch(BigInt(b * finalBatchSize), finalBatchSize, outputPtr, RANDOMX_CACHE_ACCESSES);
}
const testElapsed = (Date.now() - testStart) / 1000;
const finalRate = (testBatches * finalBatchSize) / testElapsed;
const fullDatasetTime = RANDOMX_DATASET_ITEM_COUNT / finalRate;
console.log(`Rate: ${Math.round(finalRate).toLocaleString()} items/s`);
console.log(`Estimated time: ${(fullDatasetTime / 60).toFixed(1)} minutes (single thread)`);
console.log(`With 8 workers: ~${(fullDatasetTime / 60 / 8).toFixed(1)} minutes`);
// Memory bandwidth estimate
const bytesPerItem = 64;
const cacheReadsPerItem = 8 * 64; // 8 cache line reads per item
const totalBandwidth = finalRate * (bytesPerItem + cacheReadsPerItem);
console.log(`\nMemory bandwidth: ${(totalBandwidth / 1024 / 1024 / 1024).toFixed(2)} GB/s`);