Files
Matt Hess 733ecd2681 Migrate all JS tests to Rust: 9-crate workspace, 703 tests, 0 JS remaining
Add root Cargo workspace with 9 crates: salvium-crypto (extended),
  salvium-types, salvium-consensus, salvium-wallet, salvium-tx,
  salvium-rpc, salvium-miner (extended), salvium-cli, salvium-multisig.

  New modules: chain_state, block_weight, alt_chain, validation,
  offline signing, stake lifecycle, wallet sync/query/encryption/utxo,
  randomx utilities, and full multisig crate with CARROT support.

  Delete 188 JS test/helper/debug files; archive integration test
  scripts to test/legacy-js/ for live testnet use. Testnet integration
  tests (transfer, stake, burn, convert, sweep) remain as #[ignore]-
  gated Rust tests runnable with --ignored against a live daemon.
2026-02-17 23:09:35 +00:00

193 lines
5.5 KiB
JavaScript

/**
* True Multi-threaded RandomX Benchmark using Worker Threads
*/
import { Worker } from 'worker_threads';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { writeFileSync, unlinkSync } from 'fs';
import os from 'os';
const __dirname = dirname(fileURLToPath(import.meta.url));
const BENCHMARK_DURATION = 20000; // 20 seconds
const AVAILABLE_CORES = os.cpus().length;
// Write the worker code to a file
const workerCode = `
import { parentPort, workerData } from 'worker_threads';
import { randomx_init_cache, randomx_create_vm } from '../src/randomx/vendor/index.js';
let vm = null;
let hashes = 0;
let running = false;
async function init() {
const seed = new Uint8Array(32);
const cache = randomx_init_cache(seed);
vm = randomx_create_vm(cache);
parentPort.postMessage({ type: 'ready' });
}
function mine() {
if (!running || !vm) return;
// Hash batch
for (let i = 0; i < 10 && running; i++) {
vm.calculate_hash('benchmark input ' + hashes);
hashes++;
}
if (running) {
setImmediate(mine);
}
}
parentPort.on('message', async (msg) => {
if (msg.type === 'init') {
await init();
} else if (msg.type === 'start') {
running = true;
hashes = 0;
mine();
} else if (msg.type === 'stop') {
running = false;
parentPort.postMessage({ type: 'result', hashes });
}
});
`;
const workerPath = join(__dirname, '_temp_benchmark_worker.js');
async function createWorker() {
return new Promise((resolve, reject) => {
const worker = new Worker(workerPath);
worker.once('message', (msg) => {
if (msg.type === 'ready') {
resolve(worker);
}
});
worker.on('error', reject);
worker.postMessage({ type: 'init' });
});
}
async function benchmarkThreads(numThreads) {
console.log(`\n=== Benchmarking ${numThreads} Worker Thread${numThreads > 1 ? 's' : ''} ===\n`);
// Create workers
console.log(`Creating ${numThreads} workers (each with 256MB cache)...`);
const initStart = Date.now();
const workers = [];
for (let i = 0; i < numThreads; i++) {
const worker = await createWorker();
workers.push(worker);
process.stdout.write(`\rWorker ${i + 1}/${numThreads} ready`);
}
const initTime = Date.now() - initStart;
console.log(`\nAll workers initialized in ${initTime}ms\n`);
// Start mining
console.log(`Mining for ${BENCHMARK_DURATION / 1000} seconds...`);
const startTime = Date.now();
for (const worker of workers) {
worker.postMessage({ type: 'start' });
}
// Wait for benchmark duration
await new Promise(r => setTimeout(r, BENCHMARK_DURATION));
// Stop and collect results
const results = await Promise.all(workers.map(worker => {
return new Promise(resolve => {
worker.once('message', (msg) => {
if (msg.type === 'result') {
resolve(msg.hashes);
}
});
worker.postMessage({ type: 'stop' });
});
}));
const elapsed = (Date.now() - startTime) / 1000;
const totalHashes = results.reduce((a, b) => a + b, 0);
const hashrate = totalHashes / elapsed;
// Terminate workers
for (const worker of workers) {
worker.terminate();
}
console.log(`\nResults (${numThreads} thread${numThreads > 1 ? 's' : ''}):`);
console.log(` Total hashes: ${totalHashes.toLocaleString()}`);
console.log(` Time: ${elapsed.toFixed(2)}s`);
console.log(` Hashrate: ${hashrate.toFixed(2)} H/s`);
console.log(` Per thread: ${(hashrate / numThreads).toFixed(2)} H/s`);
console.log(` Memory: ${numThreads * 256}MB`);
return { threads: numThreads, hashrate, perThread: hashrate / numThreads };
}
async function main() {
console.log('RandomX Worker Threads Benchmark');
console.log('================================');
console.log(`Available CPU cores: ${AVAILABLE_CORES}`);
console.log(`Benchmark duration: ${BENCHMARK_DURATION / 1000}s per test\n`);
// Write worker file
writeFileSync(workerPath, workerCode);
const results = [];
try {
// Test different thread counts
const threadCounts = [1, 2, 4, Math.min(8, AVAILABLE_CORES)].filter((v, i, a) => a.indexOf(v) === i);
for (const threads of threadCounts) {
const result = await benchmarkThreads(threads);
results.push(result);
}
// Summary
console.log('\n================================');
console.log('SUMMARY');
console.log('================================\n');
const baseline = results[0].hashrate;
console.log('Threads | Hashrate | Per-Thread | Memory | Scaling');
console.log('--------|-------------|-------------|----------|--------');
for (const r of results) {
const scaling = ((r.hashrate / baseline) * 100).toFixed(0);
console.log(
`${r.threads.toString().padStart(7)} | ` +
`${r.hashrate.toFixed(2).padStart(9)} H/s | ` +
`${r.perThread.toFixed(2).padStart(9)} H/s | ` +
`${(r.threads * 256).toString().padStart(4)}MB | ` +
`${scaling}%`
);
}
console.log('\n================================');
console.log('Performance Notes');
console.log('================================\n');
console.log(`Single-thread baseline: ${results[0].perThread.toFixed(2)} H/s`);
console.log(`Best multi-thread: ${Math.max(...results.map(r => r.hashrate)).toFixed(2)} H/s`);
console.log('\nScaling efficiency depends on:');
console.log(' - Memory bandwidth (256MB cache per thread)');
console.log(' - CPU cache architecture');
console.log(' - WASM runtime overhead');
} finally {
// Clean up temp file
try { unlinkSync(workerPath); } catch {}
}
}
main().catch(console.error);