|
| 1 | +import { parse } from '@bbob/parser'; |
| 2 | +import { Bench } from 'tinybench'; |
| 3 | +import xbbcode from 'xbbcode-parser'; |
| 4 | + |
| 5 | +import stub from './stub.ts'; |
| 6 | +import { testCases } from './test-cases.ts'; |
| 7 | +import yabbcode from '../dist/ya-bbcode.mjs'; |
| 8 | + |
| 9 | +function formatNumber(num: number): string { |
| 10 | + return num.toLocaleString('en-US', { maximumFractionDigits: 0 }); |
| 11 | +} |
| 12 | + |
| 13 | +interface BenchResult { |
| 14 | + name: string; |
| 15 | + opsPerSec: number; |
| 16 | + mean: number; |
| 17 | + margin: number; |
| 18 | +} |
| 19 | + |
| 20 | +async function runBenchmark(name: string, input: string, chTagOnly = false): Promise<BenchResult[]> { |
| 21 | + console.log(`\n━━━ ${name} ━━━`); |
| 22 | + console.log(`Input length: ${input.length.toLocaleString()} characters\n`); |
| 23 | + |
| 24 | + const bench = new Bench({ time: 1000 }); |
| 25 | + |
| 26 | + // ya-bbcode setup |
| 27 | + if (chTagOnly) { |
| 28 | + // For stub benchmark - only parse [ch] tag like BBob benchmark |
| 29 | + // https://github.com/JiLiZART/BBob/blob/5904ef46ed3d1c9b1827f89c5282848945d98338/benchmark/index.js |
| 30 | + bench.add('ya-bbcode', () => { |
| 31 | + const yaParser = new yabbcode(); |
| 32 | + yaParser.registerTag('ch', { |
| 33 | + type: 'replace', |
| 34 | + open: '<div>', |
| 35 | + close: '</div>', |
| 36 | + }); |
| 37 | + yaParser.parse(input); |
| 38 | + }); |
| 39 | + } else { |
| 40 | + // For other benchmarks - use default tags |
| 41 | + bench.add('ya-bbcode', () => { |
| 42 | + const yaParser = new yabbcode(); |
| 43 | + yaParser.parse(input); |
| 44 | + }); |
| 45 | + } |
| 46 | + |
| 47 | + if (chTagOnly) { |
| 48 | + // xbbcode-parser with only [ch] tag |
| 49 | + bench.add('xbbcode-parser', () => { |
| 50 | + xbbcode.addTags({ |
| 51 | + ch: { |
| 52 | + openTag: () => '<div>', |
| 53 | + closeTag: () => '</div>', |
| 54 | + restrictChildrenTo: [], |
| 55 | + }, |
| 56 | + }); |
| 57 | + return xbbcode.process({ |
| 58 | + text: input, |
| 59 | + removeMisalignedTags: false, |
| 60 | + addInLineBreaks: false, |
| 61 | + }); |
| 62 | + }); |
| 63 | + |
| 64 | + // @bbob/parser with only [ch] tag |
| 65 | + bench.add('@bbob/parser', () => { |
| 66 | + parse(input, { |
| 67 | + onlyAllowTags: ['ch'], |
| 68 | + }); |
| 69 | + }); |
| 70 | + } else { |
| 71 | + // Default parsers |
| 72 | + bench.add('xbbcode-parser', () => { |
| 73 | + xbbcode.process({ |
| 74 | + text: input, |
| 75 | + removeMisalignedTags: false, |
| 76 | + addInLineBreaks: false, |
| 77 | + }); |
| 78 | + }); |
| 79 | + |
| 80 | + bench.add('@bbob/parser', () => { |
| 81 | + parse(input); |
| 82 | + }); |
| 83 | + } |
| 84 | + |
| 85 | + await bench.run(); |
| 86 | + |
| 87 | + // Sort results by ops/sec (descending) |
| 88 | + const results: BenchResult[] = bench.tasks |
| 89 | + .map((task) => { |
| 90 | + return { |
| 91 | + name: task.name, |
| 92 | + opsPerSec: task.result?.hz || 0, |
| 93 | + mean: task.result?.mean || 0, |
| 94 | + margin: task.result?.rme || 0, |
| 95 | + }; |
| 96 | + }) |
| 97 | + .sort((resultA, resultB) => resultB.opsPerSec - resultA.opsPerSec); |
| 98 | + |
| 99 | + // Display results |
| 100 | + const maxNameLength = Math.max(...results.map(result => result.name.length)); |
| 101 | + |
| 102 | + for (const [index, result] of results.entries()) { |
| 103 | + const indicator = index === 0 ? '🏆' : (index === results.length - 1 ? '🐌' : '📊'); |
| 104 | + const name = result.name.padEnd(maxNameLength); |
| 105 | + const ops = formatNumber(result.opsPerSec).padStart(10); |
| 106 | + const fastestOps = results[0]?.opsPerSec ?? 1; |
| 107 | + const relative = index === 0 |
| 108 | + ? '(fastest)' |
| 109 | + : `(${(fastestOps / result.opsPerSec).toFixed(2)}x slower)`; |
| 110 | + |
| 111 | + console.log(`${indicator} ${name} ${ops} ops/sec ±${result.margin.toFixed(2)}% ${relative}`); |
| 112 | + } |
| 113 | + |
| 114 | + return results; |
| 115 | +} |
| 116 | + |
| 117 | +async function main() { |
| 118 | + console.log('\n╔═══════════════════════════════════════════════════════════════╗'); |
| 119 | + console.log('║ BBCode Parser Performance Comparison ║'); |
| 120 | + console.log('╚═══════════════════════════════════════════════════════════════╝'); |
| 121 | + |
| 122 | + const allResults: Record<string, BenchResult[]> = {}; |
| 123 | + |
| 124 | + // Run all benchmarks |
| 125 | + allResults['Short (Simple Bold)'] = await runBenchmark('Short (Simple Bold)', testCases.short); |
| 126 | + allResults['Medium (Nested Quote)'] = await runBenchmark('Medium (Nested Quote)', testCases.medium); |
| 127 | + allResults['Long (Forum Post)'] = await runBenchmark('Long (Forum Post)', testCases.long); |
| 128 | + allResults['Complex (Deep Nesting)'] = await runBenchmark('Complex (Deep Nesting)', testCases.complex); |
| 129 | + allResults['Deep Nesting (5 levels)'] = await runBenchmark('Deep Nesting (5 levels)', testCases.deepNesting); |
| 130 | + allResults['URL Heavy (10 links)'] = await runBenchmark('URL Heavy (10 links)', testCases.urlHeavy); |
| 131 | + allResults['List Heavy (50 items)'] = await runBenchmark('List Heavy (50 items)', testCases.listHeavy); |
| 132 | + allResults['Large Document (BBob stub)'] = await runBenchmark('Large Document (BBob stub)', stub, true); |
| 133 | + |
| 134 | + // Summary |
| 135 | + console.log('\n╔═══════════════════════════════════════════════════════════════╗'); |
| 136 | + console.log('║ Summary ║'); |
| 137 | + console.log('╚═══════════════════════════════════════════════════════════════╝\n'); |
| 138 | + |
| 139 | + const wins: Record<string, number> = { 'ya-bbcode': 0, 'xbbcode-parser': 0, '@bbob/parser': 0 }; |
| 140 | + for (const results of Object.values(allResults)) { |
| 141 | + const winner = results[0]; |
| 142 | + if (winner) { |
| 143 | + const currentWins = wins[winner.name] ?? 0; |
| 144 | + wins[winner.name] = currentWins + 1; |
| 145 | + } |
| 146 | + } |
| 147 | + |
| 148 | + console.log('Wins (fastest in category):'); |
| 149 | + for (const [parser, count] of Object.entries(wins) |
| 150 | + .sort((entryA, entryB) => entryB[1] - entryA[1])) { |
| 151 | + const percentage = ((count / Object.keys(allResults).length) * 100).toFixed(0); |
| 152 | + console.log(` ${parser}: ${count}/${Object.keys(allResults).length} (${percentage}%)`); |
| 153 | + } |
| 154 | + |
| 155 | + console.log('\n✓ Benchmark complete!\n'); |
| 156 | +} |
| 157 | + |
| 158 | +main().catch(console.error); |
0 commit comments