diff --git a/benchmark-after.txt b/benchmark-after.txt new file mode 100644 index 0000000..4a32fda --- /dev/null +++ b/benchmark-after.txt @@ -0,0 +1,12 @@ +Benchmarking "node bin/torch-lock.mjs list" (5 iterations)... +Run 1: 15199.04ms +Run 2: 15186.28ms +Run 3: 15188.74ms +Run 4: 15191.66ms +Run 5: 15184.86ms + +Results: + Count: 5 + Average: 15190.12ms + Min: 15184.86ms + Max: 15199.04ms diff --git a/benchmark-baseline.txt b/benchmark-baseline.txt new file mode 100644 index 0000000..0e551bc --- /dev/null +++ b/benchmark-baseline.txt @@ -0,0 +1,12 @@ +Benchmarking "node bin/torch-lock.mjs list" (5 iterations)... +Run 1: 15775.77ms +Run 2: 15773.07ms +Run 3: 15800.54ms +Run 4: 15770.65ms +Run 5: 16013.24ms + +Results: + Count: 5 + Average: 15826.65ms + Min: 15770.65ms + Max: 16013.24ms diff --git a/scripts/perf-benchmark.mjs b/scripts/perf-benchmark.mjs new file mode 100644 index 0000000..f35d52d --- /dev/null +++ b/scripts/perf-benchmark.mjs @@ -0,0 +1,57 @@ +import { spawn } from 'node:child_process'; +import { performance } from 'node:perf_hooks'; + +const ITERATIONS = 5; +const COMMAND = 'node'; +const ARGS = ['bin/torch-lock.mjs', 'list']; + +async function runOnce(i) { + return new Promise((resolve, reject) => { + const start = performance.now(); + const child = spawn(COMMAND, ARGS, { stdio: 'ignore' }); + + child.on('close', (code) => { + const duration = performance.now() - start; + if (code === 0) { + console.log(`Run ${i + 1}: ${duration.toFixed(2)}ms`); + resolve(duration); + } else { + reject(new Error(`Process exited with code ${code}`)); + } + }); + + child.on('error', (err) => reject(err)); + }); +} + +async function main() { + console.log(`Benchmarking "${COMMAND} ${ARGS.join(' ')}" (${ITERATIONS} iterations)...`); + const times = []; + + for (let i = 0; i < ITERATIONS; i++) { + try { + const duration = await runOnce(i); + times.push(duration); + } catch (err) { + console.error(`Run ${i + 1} failed:`, err); + } + } + + if (times.length === 0) { + console.error('No successful runs.'); + process.exit(1); + } + + const sum = times.reduce((a, b) => a + b, 0); + const avg = sum / times.length; + const min = Math.min(...times); + const max = Math.max(...times); + + console.log('\nResults:'); + console.log(` Count: ${times.length}`); + console.log(` Average: ${avg.toFixed(2)}ms`); + console.log(` Min: ${min.toFixed(2)}ms`); + console.log(` Max: ${max.toFixed(2)}ms`); +} + +main().catch(console.error); diff --git a/src/lib.mjs b/src/lib.mjs index d36b5d8..00d582f 100644 --- a/src/lib.mjs +++ b/src/lib.mjs @@ -439,9 +439,14 @@ export async function cmdList(cadence) { console.error(`Listing active locks: namespace=${namespace}, cadences=${cadences.join(', ')}`); - for (const c of cadences) { - const locks = await queryLocks(relays, c, dateStr, namespace); + const results = await Promise.all( + cadences.map(async (c) => { + const locks = await queryLocks(relays, c, dateStr, namespace); + return { c, locks }; + }), + ); + for (const { c, locks } of results) { console.log(`\n${'='.repeat(72)}`); console.log(`Active ${namespace} ${c} locks (${dateStr})`); console.log('='.repeat(72)); diff --git a/task-logs/weekly/2026-02-14T13-14-38Z__perf-deepdive-agent__completed.md b/task-logs/weekly/2026-02-14T13-14-38Z__perf-deepdive-agent__completed.md new file mode 100644 index 0000000..88184e2 --- /dev/null +++ b/task-logs/weekly/2026-02-14T13-14-38Z__perf-deepdive-agent__completed.md @@ -0,0 +1,20 @@ +--- +agent: perf-deepdive-agent +status: completed +date: 2026-02-14 +--- + +# Weekly Performance Deep Dive + +## Summary +- **Scenario**: Listing active locks (`torch-lock list`), which involves querying relays for both daily and weekly cadences. +- **Optimization**: Parallelized the relay queries in `src/lib.mjs`. +- **Baseline Duration**: ~15.8s +- **Post-Optimization Duration**: ~15.2s +- **Improvement**: ~4% + +## Analysis +The performance is dominated by a 15s timeout on one of the queries. Parallelization ensures that the total duration is determined by the slowest query, rather than the sum of sequential query times (which would be ~30s if both timed out). + +## Artifacts +- `weekly-perf-report-2026-02-14.md`: Detailed report with benchmark data. diff --git a/weekly-perf-report-2026-02-14.md b/weekly-perf-report-2026-02-14.md new file mode 100644 index 0000000..e165406 --- /dev/null +++ b/weekly-perf-report-2026-02-14.md @@ -0,0 +1,40 @@ +# Weekly Performance Report - 2026-02-14 + +## Scenario +Running `node bin/torch-lock.mjs list` to list active locks for both daily and weekly cadences. +This command executes two sequential queries to Nostr relays. + +## Baseline (Before Optimization) +Measured using `scripts/perf-benchmark.mjs` (5 iterations). + +- **Average Duration**: 15826.65ms +- **Min Duration**: 15770.65ms +- **Max Duration**: 16013.24ms + +The command appears to be network-bound, likely due to sequential execution of relay queries for each cadence. + +## After Optimization +Modified `src/lib.mjs` to execute relay queries in parallel using `Promise.all`. + +- **Average Duration**: 15190.12ms +- **Min Duration**: 15184.86ms +- **Max Duration**: 15199.04ms + +## Analysis +The speedup is approximately 4% (636ms). +While theoretically this should parallelize the latency, the observed improvement is small. +This suggests that either: +1. The relays are responding very quickly but one query dominates the time (e.g. hitting a 15s timeout). +2. The `SimplePool` instantiation overhead is significant. +3. Network bandwidth or local resource contention limits the concurrency gains. + +Given the timeout in `src/lib.mjs` is 15000ms, and the duration is just over 15000ms, it is highly likely that at least one query is timing out. +If both queries were timing out sequentially, the duration would be ~30s. +Since sequential was ~16s, it implies one query timed out (15s) and the other was very fast (<1s), OR they overlapped in some way (unlikely in sequential). + +If one query takes 15s (timeout) and the other takes 0.5s: +- Sequential: 15s + 0.5s = 15.5s. +- Parallel: max(15s, 0.5s) = 15s. +This matches the data perfectly. + +Conclusion: One cadence query (likely 'daily' or 'weekly' specifically) is timing out or very slow, while the other is fast. Parallelization ensures we pay the "slowest" cost, not the sum.