Skip to content

Commit 0a00695

Browse files
authored
Merge pull request #43 from qbunt/feature/performance-improvements
2 parents 0041c4c + 64b6cde commit 0a00695

File tree

7 files changed

+1276
-424
lines changed

7 files changed

+1276
-424
lines changed

benchmark.js

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
const romans = require('./romans')
2+
const { deromanize, romanize } = require('./romans')
3+
4+
// Benchmark configuration
5+
const ITERATIONS = {
6+
WARMUP: 1000,
7+
SMALL: 10000,
8+
MEDIUM: 100000,
9+
LARGE: 1000000
10+
}
11+
12+
// Common test values
13+
const TEST_VALUES = {
14+
SIMPLE: [1, 5, 10, 50, 100, 500, 1000],
15+
COMPLEX: [444, 999, 1994, 2023, 3999],
16+
RANDOM: Array.from({ length: 100 }, () => Math.floor(Math.random() * 3999) + 1)
17+
}
18+
19+
const TEST_ROMANS = {
20+
SIMPLE: ['I', 'V', 'X', 'L', 'C', 'D', 'M'],
21+
COMPLEX: ['CDXLIV', 'CMXCIX', 'MCMXCIV', 'MMXXIII', 'MMMCMXCIX'],
22+
RANDOM: TEST_VALUES.RANDOM.map(n => romanize(n))
23+
}
24+
25+
function benchmark(name, fn, iterations = ITERATIONS.MEDIUM) {
26+
// Warmup
27+
for (let i = 0; i < ITERATIONS.WARMUP; i++) {
28+
fn()
29+
}
30+
31+
const start = process.hrtime.bigint()
32+
for (let i = 0; i < iterations; i++) {
33+
fn()
34+
}
35+
const end = process.hrtime.bigint()
36+
37+
const durationMs = Number(end - start) / 1000000
38+
const opsPerSec = (iterations / durationMs) * 1000
39+
40+
console.log(`${name}:`)
41+
console.log(` ${iterations.toLocaleString()} ops in ${durationMs.toFixed(2)}ms`)
42+
console.log(` ${opsPerSec.toLocaleString()} ops/sec`)
43+
console.log(` ${(durationMs / iterations * 1000).toFixed(3)}μs per op`)
44+
console.log()
45+
46+
return { duration: durationMs, opsPerSec }
47+
}
48+
49+
function benchmarkRomanize() {
50+
console.log('=== ROMANIZE BENCHMARKS ===\n')
51+
52+
// Simple numbers
53+
benchmark('Simple numbers (1-1000)', () => {
54+
TEST_VALUES.SIMPLE.forEach(n => romanize(n))
55+
})
56+
57+
// Complex numbers
58+
benchmark('Complex numbers', () => {
59+
TEST_VALUES.COMPLEX.forEach(n => romanize(n))
60+
})
61+
62+
// Random numbers
63+
benchmark('Random numbers', () => {
64+
TEST_VALUES.RANDOM.forEach(n => romanize(n))
65+
})
66+
67+
// Sequential 1-3999
68+
benchmark('Sequential 1-3999', () => {
69+
for (let i = 1; i < 4000; i++) {
70+
romanize(i)
71+
}
72+
}, 100)
73+
74+
// Single value repeated
75+
benchmark('Single value (1994) repeated', () => {
76+
romanize(1994)
77+
})
78+
}
79+
80+
function benchmarkDeromanize() {
81+
console.log('=== DEROMANIZE BENCHMARKS ===\n')
82+
83+
// Simple romans
84+
benchmark('Simple romans', () => {
85+
TEST_ROMANS.SIMPLE.forEach(r => deromanize(r))
86+
})
87+
88+
// Complex romans
89+
benchmark('Complex romans', () => {
90+
TEST_ROMANS.COMPLEX.forEach(r => deromanize(r))
91+
})
92+
93+
// Random romans
94+
benchmark('Random romans', () => {
95+
TEST_ROMANS.RANDOM.forEach(r => deromanize(r))
96+
})
97+
98+
// Single value repeated
99+
benchmark('Single value (MCMXCIV) repeated', () => {
100+
deromanize('MCMXCIV')
101+
})
102+
}
103+
104+
function benchmarkRoundTrip() {
105+
console.log('=== ROUND-TRIP BENCHMARKS ===\n')
106+
107+
benchmark('Round-trip conversion', () => {
108+
TEST_VALUES.RANDOM.forEach(n => {
109+
const roman = romanize(n)
110+
deromanize(roman)
111+
})
112+
})
113+
114+
benchmark('Sequential round-trip 1-1000', () => {
115+
for (let i = 1; i <= 1000; i++) {
116+
const roman = romanize(i)
117+
deromanize(roman)
118+
}
119+
}, 1000)
120+
}
121+
122+
function memoryBenchmark() {
123+
console.log('=== MEMORY BENCHMARKS ===\n')
124+
125+
const before = process.memoryUsage()
126+
127+
// Create lots of conversions
128+
const results = []
129+
for (let i = 0; i < 100000; i++) {
130+
const num = Math.floor(Math.random() * 3999) + 1
131+
results.push(romanize(num))
132+
}
133+
134+
for (let i = 0; i < results.length; i++) {
135+
deromanize(results[i])
136+
}
137+
138+
const after = process.memoryUsage()
139+
140+
console.log('Memory usage:')
141+
console.log(` Heap Used: ${((after.heapUsed - before.heapUsed) / 1024 / 1024).toFixed(2)} MB`)
142+
console.log(` Heap Total: ${((after.heapTotal - before.heapTotal) / 1024 / 1024).toFixed(2)} MB`)
143+
console.log(` External: ${((after.external - before.external) / 1024 / 1024).toFixed(2)} MB`)
144+
console.log()
145+
}
146+
147+
function profileSpecificCases() {
148+
console.log('=== SPECIFIC CASE PROFILES ===\n')
149+
150+
// Worst case scenarios
151+
const worstCases = {
152+
'Maximum value (3999)': () => romanize(3999),
153+
'Complex subtractive (444)': () => romanize(444),
154+
'Many Ms (3000)': () => romanize(3000),
155+
'Long roman (MMMCMXCIX)': () => deromanize('MMMCMXCIX'),
156+
'Complex roman (CDXLIV)': () => deromanize('CDXLIV')
157+
}
158+
159+
Object.entries(worstCases).forEach(([name, fn]) => {
160+
benchmark(name, fn, ITERATIONS.LARGE)
161+
})
162+
}
163+
164+
function main() {
165+
console.log('Romans Library Performance Benchmark')
166+
console.log('====================================\n')
167+
168+
console.log(`Node.js ${process.version}`)
169+
console.log(`Platform: ${process.platform} ${process.arch}`)
170+
console.log(`Memory: ${Math.round(process.memoryUsage().heapTotal / 1024 / 1024)} MB\n`)
171+
172+
benchmarkRomanize()
173+
benchmarkDeromanize()
174+
benchmarkRoundTrip()
175+
profileSpecificCases()
176+
memoryBenchmark()
177+
178+
console.log('Benchmark complete!')
179+
}
180+
181+
if (require.main === module) {
182+
main()
183+
}
184+
185+
module.exports = {
186+
benchmark,
187+
benchmarkRomanize,
188+
benchmarkDeromanize,
189+
benchmarkRoundTrip,
190+
memoryBenchmark,
191+
profileSpecificCases
192+
}

0 commit comments

Comments
 (0)