|
/** |
|
* Benchmark comparing the original getBaseProps implementation vs the optimized version. |
|
* |
|
* Original: Object.keys().forEach() + regex match |
|
* Optimized: for-in loop + String.prototype.startsWith() |
|
*/ |
|
|
|
interface BaseComponentProps { |
|
className?: string; |
|
id?: string; |
|
[key: string]: any; |
|
} |
|
|
|
// Original implementation (before optimization) |
|
function getBasePropsOriginal(props: BaseComponentProps) { |
|
const baseProps: Record<string, any> = {}; |
|
Object.keys(props).forEach(prop => { |
|
if (prop === 'id' || prop === 'className' || prop.match(/^data-/)) { |
|
baseProps[prop] = (props as Record<string, any>)[prop]; |
|
} |
|
}); |
|
return baseProps as BaseComponentProps; |
|
} |
|
|
|
// Optimized implementation (after optimization) |
|
function getBasePropsOptimized(props: BaseComponentProps) { |
|
const baseProps: Record<string, any> = {}; |
|
for (const prop in props) { |
|
if (prop === 'id' || prop === 'className' || prop.startsWith('data-')) { |
|
baseProps[prop] = (props as Record<string, any>)[prop]; |
|
} |
|
} |
|
return baseProps as BaseComponentProps; |
|
} |
|
|
|
// Test data representing typical component props |
|
const testCases = { |
|
minimal: { |
|
id: 'test-id', |
|
className: 'test-class', |
|
}, |
|
withDataAttributes: { |
|
id: 'test-id', |
|
className: 'test-class', |
|
'data-testid': 'my-component', |
|
'data-analytics': 'click-event', |
|
'data-custom': 'value', |
|
}, |
|
realistic: { |
|
id: 'button-1', |
|
className: 'custom-button', |
|
'data-testid': 'submit-button', |
|
'data-analytics-id': 'submit', |
|
disabled: false, |
|
onClick: () => {}, |
|
children: 'Submit', |
|
variant: 'primary', |
|
loading: false, |
|
ariaLabel: 'Submit form', |
|
}, |
|
manyProps: { |
|
id: 'complex-component', |
|
className: 'complex-class', |
|
'data-testid': 'complex', |
|
'data-a': '1', |
|
'data-b': '2', |
|
'data-c': '3', |
|
prop1: 'value1', |
|
prop2: 'value2', |
|
prop3: 'value3', |
|
prop4: 'value4', |
|
prop5: 'value5', |
|
prop6: 'value6', |
|
prop7: 'value7', |
|
prop8: 'value8', |
|
prop9: 'value9', |
|
prop10: 'value10', |
|
}, |
|
}; |
|
|
|
function runBenchmark(name: string, fn: () => void, iterations: number): number { |
|
// Warmup |
|
for (let i = 0; i < 1000; i++) { |
|
fn(); |
|
} |
|
|
|
const start = performance.now(); |
|
for (let i = 0; i < iterations; i++) { |
|
fn(); |
|
} |
|
const end = performance.now(); |
|
return end - start; |
|
} |
|
|
|
function formatNumber(n: number): string { |
|
return n.toLocaleString('en-US', { maximumFractionDigits: 2 }); |
|
} |
|
|
|
console.log('='.repeat(80)); |
|
console.log('getBaseProps Benchmark Results'); |
|
console.log('='.repeat(80)); |
|
console.log(`Date: ${new Date().toISOString()}`); |
|
console.log(`Node.js: ${process.version}`); |
|
console.log(`Platform: ${process.platform} ${process.arch}`); |
|
console.log('='.repeat(80)); |
|
console.log(); |
|
|
|
const iterations = 1_000_000; |
|
const results: Array<{ |
|
testCase: string; |
|
originalMs: number; |
|
optimizedMs: number; |
|
speedup: number; |
|
}> = []; |
|
|
|
for (const [testCaseName, props] of Object.entries(testCases)) { |
|
console.log(`Test case: ${testCaseName}`); |
|
console.log(`Props: ${JSON.stringify(Object.keys(props))}`); |
|
console.log(`Iterations: ${formatNumber(iterations)}`); |
|
console.log(); |
|
|
|
const originalTime = runBenchmark('original', () => getBasePropsOriginal(props), iterations); |
|
const optimizedTime = runBenchmark('optimized', () => getBasePropsOptimized(props), iterations); |
|
const speedup = originalTime / optimizedTime; |
|
|
|
results.push({ |
|
testCase: testCaseName, |
|
originalMs: originalTime, |
|
optimizedMs: optimizedTime, |
|
speedup, |
|
}); |
|
|
|
console.log(` Original (Object.keys + forEach + regex): ${formatNumber(originalTime)} ms`); |
|
console.log(` Optimized (for-in + startsWith): ${formatNumber(optimizedTime)} ms`); |
|
console.log(` Speedup: ${formatNumber(speedup)}x`); |
|
console.log(); |
|
} |
|
|
|
// Verify correctness |
|
console.log('='.repeat(80)); |
|
console.log('Correctness Verification'); |
|
console.log('='.repeat(80)); |
|
|
|
let allCorrect = true; |
|
for (const [testCaseName, props] of Object.entries(testCases)) { |
|
const originalResult = getBasePropsOriginal(props); |
|
const optimizedResult = getBasePropsOptimized(props); |
|
|
|
const originalKeys = Object.keys(originalResult).sort(); |
|
const optimizedKeys = Object.keys(optimizedResult).sort(); |
|
|
|
const keysMatch = JSON.stringify(originalKeys) === JSON.stringify(optimizedKeys); |
|
const valuesMatch = originalKeys.every(key => originalResult[key] === optimizedResult[key]); |
|
|
|
const correct = keysMatch && valuesMatch; |
|
allCorrect = allCorrect && correct; |
|
|
|
console.log(`${testCaseName}: ${correct ? '✓ PASS' : '✗ FAIL'}`); |
|
if (!correct) { |
|
console.log(` Original keys: ${JSON.stringify(originalKeys)}`); |
|
console.log(` Optimized keys: ${JSON.stringify(optimizedKeys)}`); |
|
} |
|
} |
|
|
|
console.log(); |
|
console.log('='.repeat(80)); |
|
console.log('Summary'); |
|
console.log('='.repeat(80)); |
|
|
|
const avgSpeedup = results.reduce((sum, r) => sum + r.speedup, 0) / results.length; |
|
console.log(`Average speedup: ${formatNumber(avgSpeedup)}x`); |
|
console.log(`All correctness tests: ${allCorrect ? '✓ PASSED' : '✗ FAILED'}`); |
|
console.log(); |
|
|
|
// Output as markdown table for easy copy-paste |
|
console.log('='.repeat(80)); |
|
console.log('Markdown Table (for PR comment)'); |
|
console.log('='.repeat(80)); |
|
console.log(); |
|
console.log('| Test Case | Original (ms) | Optimized (ms) | Speedup |'); |
|
console.log('|-----------|---------------|----------------|---------|'); |
|
for (const r of results) { |
|
console.log(`| ${r.testCase} | ${formatNumber(r.originalMs)} | ${formatNumber(r.optimizedMs)} | ${formatNumber(r.speedup)}x |`); |
|
} |
|
console.log(`| **Average** | - | - | **${formatNumber(avgSpeedup)}x** |`); |