From 7e1c00e8b43aa03d17817c4dfed9c0eb2d4d4f15 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 15 Nov 2025 08:18:41 +0000 Subject: [PATCH 1/6] Initial plan From e49600c33b4e93d3e26af09ec950b8660b5d3820 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 15 Nov 2025 08:28:11 +0000 Subject: [PATCH 2/6] Add benchmark system with runner, generator, and sample benchmarks Co-authored-by: edouardmisset <63284636+edouardmisset@users.noreply.github.com> --- .gitignore | 1 + BENCHMARK.md | 219 +++++++++++++++++++ _internal-tools/generate-benchmarks.ts | 132 ++++++++++++ _internal-tools/run-benchmarks.ts | 279 +++++++++++++++++++++++++ array/filter-by.bench.ts | 42 ++++ array/group-by.bench.ts | 42 ++++ array/sort-by.bench.ts | 46 ++++ date/stringify-date.ts | 3 +- deno.json | 3 + math/average.bench.ts | 36 ++++ math/range.bench.ts | 33 +++ math/sum.bench.ts | 35 ++++ mod.ts | 1 - text/capitalize.bench.ts | 36 ++++ text/slugify.bench.ts | 38 ++++ text/string-equals.bench.ts | 39 ++++ 16 files changed, 983 insertions(+), 2 deletions(-) create mode 100644 BENCHMARK.md create mode 100644 _internal-tools/generate-benchmarks.ts create mode 100644 _internal-tools/run-benchmarks.ts create mode 100644 array/filter-by.bench.ts create mode 100644 array/group-by.bench.ts create mode 100644 array/sort-by.bench.ts create mode 100644 math/average.bench.ts create mode 100644 math/range.bench.ts create mode 100644 math/sum.bench.ts create mode 100644 text/capitalize.bench.ts create mode 100644 text/slugify.bench.ts create mode 100644 text/string-equals.bench.ts diff --git a/.gitignore b/.gitignore index 329427f..407a881 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ coverage docs .DS_Store +.benchmark-history diff --git a/BENCHMARK.md b/BENCHMARK.md new file mode 100644 index 0000000..a70b717 --- /dev/null +++ b/BENCHMARK.md @@ -0,0 +1,219 @@ +# Benchmarking System + +This repository includes a comprehensive benchmarking system to track and +monitor the performance of all utility functions across workspaces. + +## Overview + +The benchmark system consists of: + +1. **Benchmark files** (`.bench.ts`) - Individual benchmark files for each + function +2. **Benchmark runner** - Automated tool to run all benchmarks and collect + results +3. **Benchmark history** - JSON file tracking performance metrics over time +4. **Generator tool** - Script to generate benchmark file templates + +## Quick Start + +### Running Benchmarks + +To run all benchmarks and save results to history: + +```bash +deno task bench +``` + +Or using the full command: + +```bash +deno task bench:run +``` + +### Generating Benchmark Templates + +If you've added new functions and need benchmark templates: + +```bash +deno task bench:generate +``` + +This will create `.bench.ts` files for any functions that don't have benchmarks +yet. + +## Benchmark File Structure + +Each benchmark file follows this pattern: + +```typescript +/** + * Benchmark for functionName function + */ + +import { functionName } from './function-name.ts' + +// Test data +const testData = {/* ... */} + +Deno.bench('functionName - basic case', () => { + functionName(testData) +}) + +Deno.bench('functionName - edge case', () => { + functionName(edgeCaseData) +}) +``` + +### Best Practices for Writing Benchmarks + +1. **Use realistic data** - Test with data that represents real-world usage +2. **Test different scales** - Small, medium, and large datasets +3. **Cover edge cases** - Empty inputs, extreme values, etc. +4. **Be descriptive** - Use clear benchmark names that explain what's being + tested +5. **Avoid side effects** - Benchmarks should be pure and repeatable + +## Benchmark History + +The benchmark history is stored in `.benchmark-history/history.json` +(gitignored). + +### History Entry Structure + +Each benchmark run creates a history entry with: + +```json +{ + "timestamp": "2024-01-01T12:00:00.000Z", + "denoVersion": "2.5.6", + "v8Version": "14.0.365.5", + "typescriptVersion": "5.9.2", + "os": "linux", + "arch": "x86_64", + "cpuModel": "N/A", + "cpuCores": 4, + "memoryTotal": 0, + "packages": { + "array": "5.0.0", + "date": "5.0.0", + ... + }, + "benchmarks": { + "array/group-by": [ + { + "name": "groupBy - small dataset (5 items)", + "measuredRunsAvgMs": 0.0023, + "iterationsPerSec": 434782, + "min": 0.0020, + "max": 0.0030, + "p75": 0.0024, + "p99": 0.0028, + "p995": 0.0029 + } + ] + } +} +``` + +### Metrics Tracked + +- **measuredRunsAvgMs**: Average time per operation in milliseconds +- **iterationsPerSec**: Number of operations per second +- **min/max**: Minimum and maximum execution times +- **p75/p99/p995**: Percentile metrics for consistency analysis + +## Workspaces + +Benchmarks are organized by workspace: + +- `array/` - Array manipulation functions +- `date/` - Date utilities +- `function/` - Function utilities +- `math/` - Mathematical operations +- `object/` - Object utilities +- `text/` - Text/string processing +- `type/` - Type utilities + +## Running Individual Benchmarks + +To run benchmarks for a specific file: + +```bash +deno bench array/group-by.bench.ts +``` + +To run all benchmarks in a workspace: + +```bash +deno bench array/*.bench.ts +``` + +## Continuous Integration + +The benchmark system can be integrated into CI/CD pipelines to: + +1. Track performance over time +2. Detect performance regressions +3. Compare performance across different versions +4. Monitor the impact of code changes + +## Performance Regression Detection + +To detect performance regressions, compare the latest benchmark results with +historical data: + +1. Run benchmarks on the main branch +2. Run benchmarks on your feature branch +3. Compare the `measuredRunsAvgMs` values +4. Investigate any significant increases (>10% slower) + +## Example Output + +``` +🏃 Running benchmarks... + +System Information: + Deno: 2.5.6 + V8: 14.0.365.5 + TypeScript: 5.9.2 + OS: linux + Arch: x86_64 + CPU Cores: 4 + +Running benchmarks for array... +Running benchmarks for math... +Running benchmarks for text... + +✅ Completed 12 benchmark(s) + +📊 Results saved to .benchmark-history/history.json + +Benchmark Summary: + array/group-by: 0.0023ms avg (434782 ops/sec) + array/sort-by: 0.0156ms avg (64102 ops/sec) + math/sum: 0.0008ms avg (1250000 ops/sec) + text/capitalize: 0.0003ms avg (3333333 ops/sec) +``` + +## Maintenance + +- History is limited to the last 100 runs to keep file size manageable +- The `.benchmark-history` directory is gitignored +- Benchmark files are version-controlled alongside their source files +- Update benchmarks when function signatures or behavior changes + +## Contributing + +When adding new functions: + +1. Run `deno task bench:generate` to create template benchmark files +2. Update the generated templates with appropriate test cases +3. Run `deno task bench` to verify benchmarks work +4. Commit the benchmark files with your changes + +## Notes + +- Benchmarks should be run on a quiet system for consistent results +- Results may vary across different hardware and OS +- Use benchmark trends over time rather than absolute values +- Consider running multiple times and averaging for critical performance tests diff --git a/_internal-tools/generate-benchmarks.ts b/_internal-tools/generate-benchmarks.ts new file mode 100644 index 0000000..ad34988 --- /dev/null +++ b/_internal-tools/generate-benchmarks.ts @@ -0,0 +1,132 @@ +// deno-lint-ignore-file no-console +/** + * Generator script to create benchmark files for all functions + */ + +import { join } from '@std/path' +import { ensureFile } from '@std/fs' + +const WORKSPACES = [ + 'array', + 'date', + 'function', + 'math', + 'object', + 'text', + 'type', +] + +// Template for benchmark file +function generateBenchmarkTemplate( + functionName: string, + _workspace: string, +): string { + const importPath = `./${functionName}.ts` + const camelCaseName = functionName.replace(/-./g, (x) => x[1].toUpperCase()) + + return `/** + * Benchmark for ${functionName} function + */ + +import { ${camelCaseName} } from '${importPath}' + +// TODO: Add appropriate test data and benchmark cases +// Example: +// const testData = { /* your test data */ } + +Deno.bench('${camelCaseName} - basic case', () => { + // ${camelCaseName}(testData) + // TODO: Implement benchmark +}) + +// Add more benchmark cases as needed: +// Deno.bench('${camelCaseName} - edge case', () => { +// // ${camelCaseName}(edgeCaseData) +// }) +` +} + +/** + * Get all function files in a workspace + */ +async function getFunctionFiles(workspace: string): Promise { + const workspacePath = join(Deno.cwd(), workspace) + const functionFiles: string[] = [] + + try { + for await (const entry of Deno.readDir(workspacePath)) { + if ( + entry.isFile && + entry.name.endsWith('.ts') && + !entry.name.endsWith('.test.ts') && + !entry.name.endsWith('.bench.ts') && + entry.name !== 'mod.ts' + ) { + functionFiles.push(entry.name.replace('.ts', '')) + } + } + } catch (error) { + console.error(`Error reading workspace ${workspace}:`, error) + } + + return functionFiles +} + +/** + * Main function + */ +async function main(): Promise { + // eslint-disable-next-line no-console + console.log('📝 Generating benchmark files...\n') + + let totalCreated = 0 + let totalSkipped = 0 + + for (const workspace of WORKSPACES) { + console.log(`Processing ${workspace}...`) + const functionFiles = await getFunctionFiles(workspace) + + for (const functionName of functionFiles) { + const benchPath = join( + Deno.cwd(), + workspace, + `${functionName}.bench.ts`, + ) + + try { + // Check if benchmark file already exists + try { + await Deno.stat(benchPath) + console.log(` ⏭ Skipping ${functionName} (already exists)`) + totalSkipped++ + continue + } catch { + // File doesn't exist, create it + } + + // Create the benchmark file + const content = generateBenchmarkTemplate(functionName, workspace) + await ensureFile(benchPath) + await Deno.writeTextFile(benchPath, content) + + console.log(` ✅ Created ${functionName}.bench.ts`) + totalCreated++ + } catch (error) { + console.error( + ` ❌ Error creating benchmark for ${functionName}:`, + error, + ) + } + } + } + + console.log(`\n📊 Summary:`) + console.log(` Created: ${totalCreated}`) + console.log(` Skipped: ${totalSkipped}`) + console.log(`\n⚠ Note: Generated files contain templates only.`) + console.log(` Please update them with appropriate test data and cases.`) +} + +if (import.meta.main) { + await main() +} diff --git a/_internal-tools/run-benchmarks.ts b/_internal-tools/run-benchmarks.ts new file mode 100644 index 0000000..33e006b --- /dev/null +++ b/_internal-tools/run-benchmarks.ts @@ -0,0 +1,279 @@ +// deno-lint-ignore-file no-console +/** + * Benchmark runner that executes all benchmark files and saves results to history + */ + +import { join } from '@std/path' +import { ensureDir, exists } from '@std/fs' + +interface BenchmarkResult { + name: string + measuredRunsAvgMs: number + measuredRunsMs: number[] + totalMs: number + iterationsPerSec: number + min: number + max: number + p75: number + p99: number + p995: number +} + +interface BenchmarkHistory { + timestamp: string + denoVersion: string + v8Version: string + typescriptVersion: string + os: string + arch: string + cpuModel: string + cpuCores: number + memoryTotal: number + packages: Record + benchmarks: Record +} + +const WORKSPACES = [ + 'array', + 'date', + 'function', + 'math', + 'object', + 'text', + 'type', +] +const HISTORY_DIR = join(Deno.cwd(), '.benchmark-history') +const HISTORY_FILE = join(HISTORY_DIR, 'history.json') + +/** + * Get system information + */ +function getSystemInfo(): { + denoVersion: string + v8Version: string + typescriptVersion: string + os: string + arch: string + cpuModel: string + cpuCores: number + memoryTotal: number +} { + const versionOutput = new Deno.Command('deno', { + args: ['--version'], + }).outputSync() + + const versionText = new TextDecoder().decode(versionOutput.stdout) + const lines = versionText.split('\n') + + const denoVersion = lines[0]?.match(/deno (\S+)/)?.[1] || 'unknown' + const v8Version = lines[1]?.match(/v8 (\S+)/)?.[1] || 'unknown' + const tsVersion = lines[2]?.match(/typescript (\S+)/)?.[1] || 'unknown' + + return { + denoVersion, + v8Version, + typescriptVersion: tsVersion, + os: Deno.build.os, + arch: Deno.build.arch, + cpuModel: 'N/A', // Not easily accessible in Deno + cpuCores: navigator.hardwareConcurrency || 0, + memoryTotal: 0, // Not easily accessible in Deno + } +} + +/** + * Get package versions from workspace deno.json files + */ +async function getPackageVersions(): Promise> { + const versions: Record = {} + + for (const workspace of WORKSPACES) { + try { + const denoJsonPath = join(Deno.cwd(), workspace, 'deno.json') + const content = await Deno.readTextFile(denoJsonPath) + const json = JSON.parse(content) + versions[workspace] = json.version || 'unknown' + } catch { + versions[workspace] = 'unknown' + } + } + + return versions +} + +/** + * Run benchmarks for a specific workspace + */ +async function runWorkspaceBenchmarks( + workspace: string, +): Promise> { + const workspacePath = join(Deno.cwd(), workspace) + const results: Record = {} + + // Find all .bench.ts files + const benchFiles: string[] = [] + for await (const entry of Deno.readDir(workspacePath)) { + if (entry.isFile && entry.name.endsWith('.bench.ts')) { + benchFiles.push(join(workspacePath, entry.name)) + } + } + + if (benchFiles.length === 0) { + return results + } + + // Run benchmarks for each file + for (const benchFile of benchFiles) { + const functionName = benchFile.split('/').pop()?.replace('.bench.ts', '') || + 'unknown' + + try { + const cmd = new Deno.Command('deno', { + args: ['bench', '--json', benchFile], + stdout: 'piped', + stderr: 'piped', + }) + + const output = await cmd.output() + + if (output.success) { + const text = new TextDecoder().decode(output.stdout) + const lines = text.split('\n').filter((line) => line.trim()) + + const benchResults: BenchmarkResult[] = [] + + for (const line of lines) { + try { + const json = JSON.parse(line) + if (json.ok && json.result) { + benchResults.push({ + name: json.name || functionName, + measuredRunsAvgMs: json.result.measuredRunsAvgMs || 0, + measuredRunsMs: json.result.measuredRunsMs || [], + totalMs: json.result.totalMs || 0, + iterationsPerSec: json.result.measuredRunsAvgMs + ? 1000 / json.result.measuredRunsAvgMs + : 0, + min: json.result.min || 0, + max: json.result.max || 0, + p75: json.result.p75 || 0, + p99: json.result.p99 || 0, + p995: json.result.p995 || 0, + }) + } + } catch { + // Skip invalid JSON lines + } + } + + if (benchResults.length > 0) { + results[`${workspace}/${functionName}`] = benchResults + } + } + } catch (error) { + console.error(`Error running benchmark ${benchFile}:`, error) + } + } + + return results +} + +/** + * Load existing history + */ +async function loadHistory(): Promise { + try { + if (await exists(HISTORY_FILE)) { + const content = await Deno.readTextFile(HISTORY_FILE) + return JSON.parse(content) + } + } catch (error) { + console.warn('Could not load history:', error) + } + return [] +} + +/** + * Save history + */ +async function saveHistory(history: BenchmarkHistory[]): Promise { + await ensureDir(HISTORY_DIR) + await Deno.writeTextFile( + HISTORY_FILE, + JSON.stringify(history, null, 2), + ) +} + +/** + * Main function + */ +async function main(): Promise { + console.log('🏃 Running benchmarks...\n') + + const systemInfo = getSystemInfo() + const packages = await getPackageVersions() + + console.log('System Information:') + console.log(` Deno: ${systemInfo.denoVersion}`) + console.log(` V8: ${systemInfo.v8Version}`) + console.log(` TypeScript: ${systemInfo.typescriptVersion}`) + console.log(` OS: ${systemInfo.os}`) + console.log(` Arch: ${systemInfo.arch}`) + console.log(` CPU Cores: ${systemInfo.cpuCores}\n`) + + // Run benchmarks for all workspaces + const allBenchmarks: Record = {} + + for (const workspace of WORKSPACES) { + console.log(`Running benchmarks for ${workspace}...`) + const results = await runWorkspaceBenchmarks(workspace) + Object.assign(allBenchmarks, results) + } + + const totalBenchmarks = Object.keys(allBenchmarks).length + console.log(`\n✅ Completed ${totalBenchmarks} benchmark(s)\n`) + + // Create history entry + const historyEntry: BenchmarkHistory = { + timestamp: new Date().toISOString(), + denoVersion: systemInfo.denoVersion, + v8Version: systemInfo.v8Version, + typescriptVersion: systemInfo.typescriptVersion, + os: systemInfo.os, + arch: systemInfo.arch, + cpuModel: systemInfo.cpuModel, + cpuCores: systemInfo.cpuCores, + memoryTotal: systemInfo.memoryTotal, + packages, + benchmarks: allBenchmarks, + } + + // Load and update history + const history = await loadHistory() + history.push(historyEntry) + + // Keep only last 100 entries + if (history.length > 100) { + history.splice(0, history.length - 100) + } + + await saveHistory(history) + + console.log(`📊 Results saved to ${HISTORY_FILE}`) + + // Display summary + if (totalBenchmarks > 0) { + console.log('\nBenchmark Summary:') + for (const [name, results] of Object.entries(allBenchmarks)) { + if (results.length > 0) { + const avgTime = results[0].measuredRunsAvgMs.toFixed(4) + const opsPerSec = results[0].iterationsPerSec.toFixed(0) + console.log(` ${name}: ${avgTime}ms avg (${opsPerSec} ops/sec)`) + } + } + } +} + +if (import.meta.main) { + await main() +} diff --git a/array/filter-by.bench.ts b/array/filter-by.bench.ts new file mode 100644 index 0000000..6a162b5 --- /dev/null +++ b/array/filter-by.bench.ts @@ -0,0 +1,42 @@ +/** + * Benchmark for filterBy function + */ + +import { filterBy } from './filter-by.ts' + +// Test data +const smallData = [ + { id: 1, active: true, score: 85 }, + { id: 2, active: false, score: 92 }, + { id: 3, active: true, score: 78 }, + { id: 4, active: true, score: 95 }, + { id: 5, active: false, score: 88 }, +] + +const mediumData = Array.from({ length: 100 }, (_, i) => ({ + id: i, + active: i % 2 === 0, + score: 50 + (i % 50), +})) + +const largeData = Array.from({ length: 1000 }, (_, i) => ({ + id: i, + active: i % 2 === 0, + score: 50 + (i % 50), +})) + +Deno.bench('filterBy - small dataset by boolean', () => { + filterBy(smallData, 'active', true) +}) + +Deno.bench('filterBy - small dataset by number', () => { + filterBy(smallData, 'score', 90) +}) + +Deno.bench('filterBy - medium dataset (100 items)', () => { + filterBy(mediumData, 'active', true) +}) + +Deno.bench('filterBy - large dataset (1000 items)', () => { + filterBy(largeData, 'active', true) +}) diff --git a/array/group-by.bench.ts b/array/group-by.bench.ts new file mode 100644 index 0000000..5080e94 --- /dev/null +++ b/array/group-by.bench.ts @@ -0,0 +1,42 @@ +/** + * Benchmark for groupBy function + */ + +import { groupBy } from './group-by.ts' + +// Test data +const smallData = [ + { id: 1, name: 'Object 1', category: 'A' }, + { id: 2, name: 'Object 2', category: 'B' }, + { id: 3, name: 'Object 3', category: 'A' }, + { id: 4, name: 'Object 4', category: 'C' }, + { id: 5, name: 'Object 5', category: 'B' }, +] + +const mediumData = Array.from({ length: 100 }, (_, i) => ({ + id: i, + name: `Object ${i}`, + category: ['A', 'B', 'C', 'D', 'E'][i % 5], +})) + +const largeData = Array.from({ length: 1000 }, (_, i) => ({ + id: i, + name: `Object ${i}`, + category: ['A', 'B', 'C', 'D', 'E'][i % 5], +})) + +Deno.bench('groupBy - small dataset (5 items)', () => { + groupBy(smallData, 'category') +}) + +Deno.bench('groupBy - medium dataset (100 items)', () => { + groupBy(mediumData, 'category') +}) + +Deno.bench('groupBy - large dataset (1000 items)', () => { + groupBy(largeData, 'category') +}) + +Deno.bench('groupBy - group by id (unique values)', () => { + groupBy(mediumData, 'id') +}) diff --git a/array/sort-by.bench.ts b/array/sort-by.bench.ts new file mode 100644 index 0000000..0c8eb85 --- /dev/null +++ b/array/sort-by.bench.ts @@ -0,0 +1,46 @@ +/** + * Benchmark for sortBy function + */ + +import { sortBy } from './sort-by.ts' + +// Test data +const smallData = [ + { id: 5, name: 'Eve', age: 35 }, + { id: 2, name: 'Bob', age: 25 }, + { id: 4, name: 'Dave', age: 30 }, + { id: 1, name: 'Alice', age: 20 }, + { id: 3, name: 'Carol', age: 28 }, +] + +const mediumData = Array.from({ length: 100 }, (_, i) => ({ + id: 100 - i, + name: `User${i}`, + age: 20 + (i % 50), +})) + +const largeData = Array.from({ length: 1000 }, (_, i) => ({ + id: 1000 - i, + name: `User${i}`, + age: 20 + (i % 50), +})) + +Deno.bench('sortBy - small dataset by number', () => { + sortBy(smallData, 'id') +}) + +Deno.bench('sortBy - small dataset by string', () => { + sortBy(smallData, 'name') +}) + +Deno.bench('sortBy - medium dataset (100 items)', () => { + sortBy(mediumData, 'id') +}) + +Deno.bench('sortBy - large dataset (1000 items)', () => { + sortBy(largeData, 'id') +}) + +Deno.bench('sortBy - with order desc', () => { + sortBy(mediumData, 'id', 'desc') +}) diff --git a/date/stringify-date.ts b/date/stringify-date.ts index 33f419d..8a9f74d 100644 --- a/date/stringify-date.ts +++ b/date/stringify-date.ts @@ -33,7 +33,8 @@ export function stringifyDate( if (!isValidDate(date)) { return err( new TypeError( - `Expected a valid Date object for ${String(date)} but got ${String(typeof date) + `Expected a valid Date object for ${String(date)} but got ${ + String(typeof date) }`, ), ) diff --git a/deno.json b/deno.json index 4a48b59..d826656 100644 --- a/deno.json +++ b/deno.json @@ -1,6 +1,9 @@ { "$schema": "https://raw.githubusercontent.com/denoland/deno/main/cli/schemas/config-file.v1.json", "tasks": { + "bench:generate": "deno run --allow-read --allow-write ./_internal-tools/generate-benchmarks.ts", + "bench:run": "deno run --allow-read --allow-write --allow-run ./_internal-tools/run-benchmarks.ts", + "bench": "deno task bench:run", "check:exports": "deno run --allow-read --allow-write --allow-run ./_internal-tools/check-exports.ts", "check:imports": "deno run --allow-read ./_internal-tools/check-imports.ts", "check:links": "deno run --allow-read ./_internal-tools/check-links.ts", diff --git a/math/average.bench.ts b/math/average.bench.ts new file mode 100644 index 0000000..4f827c1 --- /dev/null +++ b/math/average.bench.ts @@ -0,0 +1,36 @@ +/** + * Benchmark for average function + */ + +import { average } from './average.ts' + +// Test data +const smallArray = [1, 2, 3, 4, 5] +const mediumArray = Array.from({ length: 100 }, (_, i) => i) +const largeArray = Array.from({ length: 1000 }, (_, i) => i) +const decimalsArray = [1.5, 2.7, 3.2, 4.8, 5.1] +const negativeArray = [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5] + +Deno.bench('average - small array (5 elements)', () => { + average(smallArray) +}) + +Deno.bench('average - medium array (100 elements)', () => { + average(mediumArray) +}) + +Deno.bench('average - large array (1000 elements)', () => { + average(largeArray) +}) + +Deno.bench('average - decimal numbers', () => { + average(decimalsArray) +}) + +Deno.bench('average - negative numbers', () => { + average(negativeArray) +}) + +Deno.bench('average - single element', () => { + average([42]) +}) diff --git a/math/range.bench.ts b/math/range.bench.ts new file mode 100644 index 0000000..6966992 --- /dev/null +++ b/math/range.bench.ts @@ -0,0 +1,33 @@ +/** + * Benchmark for range function + */ + +import { range } from './range.ts' + +Deno.bench('range - small range (0-10)', () => { + range(0, 10) +}) + +Deno.bench('range - medium range (0-100)', () => { + range(0, 100) +}) + +Deno.bench('range - large range (0-1000)', () => { + range(0, 1000) +}) + +Deno.bench('range - with step of 2', () => { + range(0, 100, 2) +}) + +Deno.bench('range - with step of 10', () => { + range(0, 1000, 10) +}) + +Deno.bench('range - negative range', () => { + range(-50, 50) +}) + +Deno.bench('range - reverse range (10-0)', () => { + range(10, 0, -1) +}) diff --git a/math/sum.bench.ts b/math/sum.bench.ts new file mode 100644 index 0000000..535714e --- /dev/null +++ b/math/sum.bench.ts @@ -0,0 +1,35 @@ +/** + * Benchmark for sum function + */ + +import { sum } from './sum.ts' + +// Test data +const smallArray = [1, 2, 3, 4, 5] +const mediumArray = Array.from({ length: 100 }, (_, i) => i) +const largeArray = Array.from({ length: 1000 }, (_, i) => i) +const multipleArrays = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] + +Deno.bench('sum - multiple arguments', () => { + sum(1, 2, 3, 4, 5) +}) + +Deno.bench('sum - small array', () => { + sum(smallArray) +}) + +Deno.bench('sum - medium array (100 elements)', () => { + sum(mediumArray) +}) + +Deno.bench('sum - large array (1000 elements)', () => { + sum(largeArray) +}) + +Deno.bench('sum - multiple arrays', () => { + sum(...multipleArrays) +}) + +Deno.bench('sum - empty array', () => { + sum([]) +}) diff --git a/mod.ts b/mod.ts index 3c9b8e3..a6de0d5 100644 --- a/mod.ts +++ b/mod.ts @@ -48,4 +48,3 @@ export * from './math/mod.ts' export * from './object/mod.ts' export * from './text/mod.ts' export * from './type/mod.ts' - diff --git a/text/capitalize.bench.ts b/text/capitalize.bench.ts new file mode 100644 index 0000000..6a68745 --- /dev/null +++ b/text/capitalize.bench.ts @@ -0,0 +1,36 @@ +/** + * Benchmark for capitalize function + */ + +import { capitalize } from './capitalize.ts' + +// Test data +const shortString = 'hello' +const longString = + 'hello world this is a much longer string to test performance' +const upperString = 'HELLO WORLD' +const mixedString = 'hElLo WoRlD' + +Deno.bench('capitalize - short string', () => { + capitalize(shortString) +}) + +Deno.bench('capitalize - long string', () => { + capitalize(longString) +}) + +Deno.bench('capitalize - uppercase string', () => { + capitalize(upperString) +}) + +Deno.bench('capitalize - mixed case string', () => { + capitalize(mixedString) +}) + +Deno.bench('capitalize - with lowercase option false', () => { + capitalize(mixedString, { lowercase: false }) +}) + +Deno.bench('capitalize - empty string', () => { + capitalize('') +}) diff --git a/text/slugify.bench.ts b/text/slugify.bench.ts new file mode 100644 index 0000000..e664b2e --- /dev/null +++ b/text/slugify.bench.ts @@ -0,0 +1,38 @@ +/** + * Benchmark for slugify function + */ + +import { slugify } from './slugify.ts' + +// Test data +const simple = 'Hello World' +const withAccents = 'HĂ©llo Wörld CafĂ©' +const withSpecialChars = 'Hello! World? How are you?' +const longText = + 'This is a much longer text with many words and special characters! It should test performance.' +const multipleSpaces = 'Hello World With Multiple Spaces' +const mixedCase = 'HeLLo WoRLd MiXeD CaSe' + +Deno.bench('slugify - simple text', () => { + slugify(simple) +}) + +Deno.bench('slugify - with accents', () => { + slugify(withAccents) +}) + +Deno.bench('slugify - with special characters', () => { + slugify(withSpecialChars) +}) + +Deno.bench('slugify - long text', () => { + slugify(longText) +}) + +Deno.bench('slugify - multiple spaces', () => { + slugify(multipleSpaces) +}) + +Deno.bench('slugify - mixed case', () => { + slugify(mixedCase) +}) diff --git a/text/string-equals.bench.ts b/text/string-equals.bench.ts new file mode 100644 index 0000000..4634078 --- /dev/null +++ b/text/string-equals.bench.ts @@ -0,0 +1,39 @@ +/** + * Benchmark for stringEquals function + */ + +import { stringEquals } from './string-equals.ts' + +// Test data +const str1 = 'Hello World' +const str2 = 'hello world' +const str3 = 'HELLO WORLD' +const str4 = 'Different String' +const longStr1 = + 'This is a much longer string to test performance with case insensitive comparison' +const longStr2 = + 'THIS IS A MUCH LONGER STRING TO TEST PERFORMANCE WITH CASE INSENSITIVE COMPARISON' + +Deno.bench('stringEquals - exact match', () => { + stringEquals(str1, str1) +}) + +Deno.bench('stringEquals - case insensitive match', () => { + stringEquals(str1, str2) +}) + +Deno.bench('stringEquals - uppercase match', () => { + stringEquals(str1, str3) +}) + +Deno.bench('stringEquals - no match', () => { + stringEquals(str1, str4) +}) + +Deno.bench('stringEquals - long strings match', () => { + stringEquals(longStr1, longStr2) +}) + +Deno.bench('stringEquals - empty strings', () => { + stringEquals('', '') +}) From b878e575532b3701aeee06ae494526f3d99db36f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 15 Nov 2025 08:31:23 +0000 Subject: [PATCH 3/6] Add comprehensive benchmark files for multiple workspaces Co-authored-by: edouardmisset <63284636+edouardmisset@users.noreply.github.com> --- array/find-by.bench.ts | 46 ++++++++++++++++++++++++++ array/max-by.bench.ts | 36 ++++++++++++++++++++ array/min-by.bench.ts | 36 ++++++++++++++++++++ array/take.bench.ts | 30 +++++++++++++++++ date/is-valid-date.bench.ts | 40 ++++++++++++++++++++++ date/parse-date.bench.ts | 31 +++++++++++++++++ date/sort-by-date.bench.ts | 38 +++++++++++++++++++++ function/is-empty.bench.ts | 57 ++++++++++++++++++++++++++++++++ function/memoization.bench.ts | 40 ++++++++++++++++++++++ function/pipe.bench.ts | 37 +++++++++++++++++++++ math/clamp.bench.ts | 33 ++++++++++++++++++ math/normalize.bench.ts | 31 +++++++++++++++++ math/product.bench.ts | 35 ++++++++++++++++++++ math/round-to-precision.bench.ts | 28 ++++++++++++++++ object/is-object.bench.ts | 52 +++++++++++++++++++++++++++++ object/omit.bench.ts | 54 ++++++++++++++++++++++++++++++ object/pick.bench.ts | 53 +++++++++++++++++++++++++++++ text/remove-accents.bench.ts | 37 +++++++++++++++++++++ text/string-includes.bench.ts | 38 +++++++++++++++++++++ text/string-to-boolean.bench.ts | 45 +++++++++++++++++++++++++ 20 files changed, 797 insertions(+) create mode 100644 array/find-by.bench.ts create mode 100644 array/max-by.bench.ts create mode 100644 array/min-by.bench.ts create mode 100644 array/take.bench.ts create mode 100644 date/is-valid-date.bench.ts create mode 100644 date/parse-date.bench.ts create mode 100644 date/sort-by-date.bench.ts create mode 100644 function/is-empty.bench.ts create mode 100644 function/memoization.bench.ts create mode 100644 function/pipe.bench.ts create mode 100644 math/clamp.bench.ts create mode 100644 math/normalize.bench.ts create mode 100644 math/product.bench.ts create mode 100644 math/round-to-precision.bench.ts create mode 100644 object/is-object.bench.ts create mode 100644 object/omit.bench.ts create mode 100644 object/pick.bench.ts create mode 100644 text/remove-accents.bench.ts create mode 100644 text/string-includes.bench.ts create mode 100644 text/string-to-boolean.bench.ts diff --git a/array/find-by.bench.ts b/array/find-by.bench.ts new file mode 100644 index 0000000..75e1d72 --- /dev/null +++ b/array/find-by.bench.ts @@ -0,0 +1,46 @@ +/** + * Benchmark for findBy function + */ + +import { findBy } from './find-by.ts' + +// Test data +const smallData = [ + { id: 1, name: 'Alice', active: true }, + { id: 2, name: 'Bob', active: false }, + { id: 3, name: 'Carol', active: true }, + { id: 4, name: 'Dave', active: true }, + { id: 5, name: 'Eve', active: false }, +] + +const mediumData = Array.from({ length: 100 }, (_, i) => ({ + id: i, + name: `User${i}`, + active: i % 2 === 0, +})) + +const largeData = Array.from({ length: 1000 }, (_, i) => ({ + id: i, + name: `User${i}`, + active: i % 2 === 0, +})) + +Deno.bench('findBy - small dataset, first item', () => { + findBy(smallData, 'id', 1) +}) + +Deno.bench('findBy - small dataset, last item', () => { + findBy(smallData, 'id', 5) +}) + +Deno.bench('findBy - medium dataset (100 items)', () => { + findBy(mediumData, 'id', 50) +}) + +Deno.bench('findBy - large dataset (1000 items)', () => { + findBy(largeData, 'id', 500) +}) + +Deno.bench('findBy - not found', () => { + findBy(smallData, 'id', 999) +}) diff --git a/array/max-by.bench.ts b/array/max-by.bench.ts new file mode 100644 index 0000000..b7bb3a7 --- /dev/null +++ b/array/max-by.bench.ts @@ -0,0 +1,36 @@ +/** + * Benchmark for maxBy function + */ + +import { maxBy } from './max-by.ts' + +// Test data +const smallData = [ + { id: 1, value: 10 }, + { id: 2, value: 50 }, + { id: 3, value: 30 }, + { id: 4, value: 90 }, + { id: 5, value: 20 }, +] + +const mediumData = Array.from({ length: 100 }, (_, i) => ({ + id: i, + value: Math.random() * 100, +})) + +const largeData = Array.from({ length: 1000 }, (_, i) => ({ + id: i, + value: Math.random() * 100, +})) + +Deno.bench('maxBy - small dataset', () => { + maxBy(smallData, 'value') +}) + +Deno.bench('maxBy - medium dataset (100 items)', () => { + maxBy(mediumData, 'value') +}) + +Deno.bench('maxBy - large dataset (1000 items)', () => { + maxBy(largeData, 'value') +}) diff --git a/array/min-by.bench.ts b/array/min-by.bench.ts new file mode 100644 index 0000000..b55ecc8 --- /dev/null +++ b/array/min-by.bench.ts @@ -0,0 +1,36 @@ +/** + * Benchmark for minBy function + */ + +import { minBy } from './min-by.ts' + +// Test data +const smallData = [ + { id: 1, value: 10 }, + { id: 2, value: 50 }, + { id: 3, value: 30 }, + { id: 4, value: 90 }, + { id: 5, value: 20 }, +] + +const mediumData = Array.from({ length: 100 }, (_, i) => ({ + id: i, + value: Math.random() * 100, +})) + +const largeData = Array.from({ length: 1000 }, (_, i) => ({ + id: i, + value: Math.random() * 100, +})) + +Deno.bench('minBy - small dataset', () => { + minBy(smallData, 'value') +}) + +Deno.bench('minBy - medium dataset (100 items)', () => { + minBy(mediumData, 'value') +}) + +Deno.bench('minBy - large dataset (1000 items)', () => { + minBy(largeData, 'value') +}) diff --git a/array/take.bench.ts b/array/take.bench.ts new file mode 100644 index 0000000..d40977a --- /dev/null +++ b/array/take.bench.ts @@ -0,0 +1,30 @@ +/** + * Benchmark for take function + */ + +import { take } from './take.ts' + +// Test data +const smallArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +const mediumArray = Array.from({ length: 100 }, (_, i) => i) +const largeArray = Array.from({ length: 1000 }, (_, i) => i) + +Deno.bench('take - first 5 from small array', () => { + take(smallArray, 5) +}) + +Deno.bench('take - first 10 from medium array', () => { + take(mediumArray, 10) +}) + +Deno.bench('take - first 50 from large array', () => { + take(largeArray, 50) +}) + +Deno.bench('take - more than available', () => { + take(smallArray, 20) +}) + +Deno.bench('take - zero items', () => { + take(smallArray, 0) +}) diff --git a/date/is-valid-date.bench.ts b/date/is-valid-date.bench.ts new file mode 100644 index 0000000..024d6c8 --- /dev/null +++ b/date/is-valid-date.bench.ts @@ -0,0 +1,40 @@ +/** + * Benchmark for isValidDate function + */ + +import { isValidDate } from './is-valid-date.ts' + +// Test data +const validDate = new Date('2024-01-15') +const invalidDate = new Date('invalid') +const validString = '2024-01-15' +const invalidString = 'not-a-date' +const timestamp = 1705318200000 + +Deno.bench('isValidDate - valid Date object', () => { + isValidDate(validDate) +}) + +Deno.bench('isValidDate - invalid Date object', () => { + isValidDate(invalidDate) +}) + +Deno.bench('isValidDate - valid date string', () => { + isValidDate(validString) +}) + +Deno.bench('isValidDate - invalid string', () => { + isValidDate(invalidString) +}) + +Deno.bench('isValidDate - timestamp', () => { + isValidDate(timestamp) +}) + +Deno.bench('isValidDate - undefined', () => { + isValidDate(undefined) +}) + +Deno.bench('isValidDate - null', () => { + isValidDate(null) +}) diff --git a/date/parse-date.bench.ts b/date/parse-date.bench.ts new file mode 100644 index 0000000..6e9bfbe --- /dev/null +++ b/date/parse-date.bench.ts @@ -0,0 +1,31 @@ +/** + * Benchmark for parseDate function + */ + +import { parseDate } from './parse-date.ts' + +// Test data - various date formats +const isoDate = '2024-01-15T10:30:00.000Z' +const shortDate = '2024-01-15' +const timestamp = 1705318200000 +const dateObject = new Date('2024-01-15') + +Deno.bench('parseDate - ISO string', () => { + parseDate(isoDate) +}) + +Deno.bench('parseDate - short date string', () => { + parseDate(shortDate) +}) + +Deno.bench('parseDate - timestamp number', () => { + parseDate(timestamp) +}) + +Deno.bench('parseDate - Date object', () => { + parseDate(dateObject) +}) + +Deno.bench('parseDate - undefined', () => { + parseDate(undefined) +}) diff --git a/date/sort-by-date.bench.ts b/date/sort-by-date.bench.ts new file mode 100644 index 0000000..d240113 --- /dev/null +++ b/date/sort-by-date.bench.ts @@ -0,0 +1,38 @@ +/** + * Benchmark for sortByDate function + */ + +import { sortByDate } from './sort-by-date.ts' + +// Test data +const smallDates = [ + { date: new Date('2024-03-15') }, + { date: new Date('2024-01-10') }, + { date: new Date('2024-05-20') }, + { date: new Date('2024-02-14') }, + { date: new Date('2024-04-01') }, +] + +const mediumDates = Array.from({ length: 100 }, (_, i) => ({ + date: new Date(2024, 0, 1 + i), +})) + +const largeDates = Array.from({ length: 1000 }, (_, i) => ({ + date: new Date(2024, 0, 1 + (i % 365)), +})) + +Deno.bench('sortByDate - small dataset', () => { + sortByDate(smallDates, 'date') +}) + +Deno.bench('sortByDate - medium dataset (100 items)', () => { + sortByDate(mediumDates, 'date') +}) + +Deno.bench('sortByDate - large dataset (1000 items)', () => { + sortByDate(largeDates, 'date') +}) + +Deno.bench('sortByDate - descending order', () => { + sortByDate(smallDates, 'date', 'desc') +}) diff --git a/function/is-empty.bench.ts b/function/is-empty.bench.ts new file mode 100644 index 0000000..173ec86 --- /dev/null +++ b/function/is-empty.bench.ts @@ -0,0 +1,57 @@ +/** + * Benchmark for isEmpty function + */ + +import { isEmpty } from './is-empty.ts' + +// Test data +const emptyString = '' +const nonEmptyString = 'hello' +const emptyArray: unknown[] = [] +const nonEmptyArray = [1, 2, 3] +const emptyObject = {} +const nonEmptyObject = { key: 'value' } +const nullValue = null +const undefinedValue = undefined +const zero = 0 +const falsyValue = false + +Deno.bench('isEmpty - empty string', () => { + isEmpty(emptyString) +}) + +Deno.bench('isEmpty - non-empty string', () => { + isEmpty(nonEmptyString) +}) + +Deno.bench('isEmpty - empty array', () => { + isEmpty(emptyArray) +}) + +Deno.bench('isEmpty - non-empty array', () => { + isEmpty(nonEmptyArray) +}) + +Deno.bench('isEmpty - empty object', () => { + isEmpty(emptyObject) +}) + +Deno.bench('isEmpty - non-empty object', () => { + isEmpty(nonEmptyObject) +}) + +Deno.bench('isEmpty - null', () => { + isEmpty(nullValue) +}) + +Deno.bench('isEmpty - undefined', () => { + isEmpty(undefinedValue) +}) + +Deno.bench('isEmpty - zero', () => { + isEmpty(zero) +}) + +Deno.bench('isEmpty - false', () => { + isEmpty(falsyValue) +}) diff --git a/function/memoization.bench.ts b/function/memoization.bench.ts new file mode 100644 index 0000000..2b1449c --- /dev/null +++ b/function/memoization.bench.ts @@ -0,0 +1,40 @@ +/** + * Benchmark for memoization function + */ + +import { memoization } from './memoization.ts' + +// Test functions to memoize +const fibonacci = (n: number): number => { + if (n <= 1) return n + return fibonacci(n - 1) + fibonacci(n - 2) +} + +const add = (a: number, b: number): number => a + b +const multiply = (a: number, b: number): number => a * b + +// Memoized versions +const memoizedAdd = memoization(add) +const memoizedMultiply = memoization(multiply) + +Deno.bench('memoization - first call (cache miss)', () => { + const memoAdd = memoization(add) + memoAdd(5, 3) +}) + +Deno.bench('memoization - repeated call (cache hit)', () => { + memoizedAdd(10, 20) +}) + +Deno.bench('memoization - different arguments', () => { + memoizedAdd(Math.random(), Math.random()) +}) + +Deno.bench('memoization - multiply operation', () => { + memoizedMultiply(7, 8) +}) + +Deno.bench('memoization - string arguments', () => { + const memoConcat = memoization((a: string, b: string) => a + b) + memoConcat('hello', 'world') +}) diff --git a/function/pipe.bench.ts b/function/pipe.bench.ts new file mode 100644 index 0000000..d3f34fb --- /dev/null +++ b/function/pipe.bench.ts @@ -0,0 +1,37 @@ +/** + * Benchmark for pipe function + */ + +import { pipe } from './pipe.ts' + +// Test functions +const add5 = (n: number): number => n + 5 +const multiply2 = (n: number): number => n * 2 +const subtract3 = (n: number): number => n - 3 +const square = (n: number): number => n * n + +Deno.bench('pipe - 2 functions', () => { + const fn = pipe(add5, multiply2) + fn(10) +}) + +Deno.bench('pipe - 3 functions', () => { + const fn = pipe(add5, multiply2, subtract3) + fn(10) +}) + +Deno.bench('pipe - 4 functions', () => { + const fn = pipe(add5, multiply2, subtract3, square) + fn(10) +}) + +Deno.bench('pipe - complex chain', () => { + const fn = pipe( + (n: number) => n + 1, + (n: number) => n * 2, + (n: number) => n - 5, + (n: number) => n / 3, + (n: number) => Math.floor(n), + ) + fn(20) +}) diff --git a/math/clamp.bench.ts b/math/clamp.bench.ts new file mode 100644 index 0000000..df5ecdc --- /dev/null +++ b/math/clamp.bench.ts @@ -0,0 +1,33 @@ +/** + * Benchmark for clamp function + */ + +import { clamp } from './clamp.ts' + +Deno.bench('clamp - value within range', () => { + clamp(5, 0, 10) +}) + +Deno.bench('clamp - value below minimum', () => { + clamp(-5, 0, 10) +}) + +Deno.bench('clamp - value above maximum', () => { + clamp(15, 0, 10) +}) + +Deno.bench('clamp - value at minimum', () => { + clamp(0, 0, 10) +}) + +Deno.bench('clamp - value at maximum', () => { + clamp(10, 0, 10) +}) + +Deno.bench('clamp - negative range', () => { + clamp(-5, -10, -1) +}) + +Deno.bench('clamp - decimal values', () => { + clamp(5.5, 0.5, 10.5) +}) diff --git a/math/normalize.bench.ts b/math/normalize.bench.ts new file mode 100644 index 0000000..8766438 --- /dev/null +++ b/math/normalize.bench.ts @@ -0,0 +1,31 @@ +/** + * Benchmark for normalize function + */ + +import { normalize } from './normalize.ts' + +// Test data +const smallArray = [1, 2, 3, 4, 5] +const mediumArray = Array.from({ length: 100 }, (_, i) => i) +const largeArray = Array.from({ length: 1000 }, (_, i) => i) +const negativeArray = [-5, -3, -1, 1, 3, 5] + +Deno.bench('normalize - small array', () => { + normalize(smallArray) +}) + +Deno.bench('normalize - medium array (100 elements)', () => { + normalize(mediumArray) +}) + +Deno.bench('normalize - large array (1000 elements)', () => { + normalize(largeArray) +}) + +Deno.bench('normalize - negative values', () => { + normalize(negativeArray) +}) + +Deno.bench('normalize - single element', () => { + normalize([42]) +}) diff --git a/math/product.bench.ts b/math/product.bench.ts new file mode 100644 index 0000000..f8f5d10 --- /dev/null +++ b/math/product.bench.ts @@ -0,0 +1,35 @@ +/** + * Benchmark for product function + */ + +import { product } from './product.ts' + +// Test data +const smallArray = [1, 2, 3, 4, 5] +const mediumArray = Array.from({ length: 10 }, (_, i) => i + 1) +const largeArray = Array.from({ length: 20 }, (_, i) => i + 1) +const decimals = [1.5, 2.5, 3.5] + +Deno.bench('product - small array (5 elements)', () => { + product(smallArray) +}) + +Deno.bench('product - medium array (10 elements)', () => { + product(mediumArray) +}) + +Deno.bench('product - large array (20 elements)', () => { + product(largeArray) +}) + +Deno.bench('product - decimal numbers', () => { + product(decimals) +}) + +Deno.bench('product - single element', () => { + product([42]) +}) + +Deno.bench('product - with zero', () => { + product([1, 2, 0, 4, 5]) +}) diff --git a/math/round-to-precision.bench.ts b/math/round-to-precision.bench.ts new file mode 100644 index 0000000..fb6e55d --- /dev/null +++ b/math/round-to-precision.bench.ts @@ -0,0 +1,28 @@ +/** + * Benchmark for roundToPrecision function + */ + +import { roundToPrecision } from './round-to-precision.ts' + +// Test data +const decimals = [1.2345, 2.6789, 3.1415, 4.5678, 5.9876] + +Deno.bench('roundToPrecision - precision 0', () => { + decimals.forEach((n) => roundToPrecision(n, 0)) +}) + +Deno.bench('roundToPrecision - precision 1', () => { + decimals.forEach((n) => roundToPrecision(n, 1)) +}) + +Deno.bench('roundToPrecision - precision 2', () => { + decimals.forEach((n) => roundToPrecision(n, 2)) +}) + +Deno.bench('roundToPrecision - precision 3', () => { + decimals.forEach((n) => roundToPrecision(n, 3)) +}) + +Deno.bench('roundToPrecision - negative precision', () => { + roundToPrecision(12345, -2) +}) diff --git a/object/is-object.bench.ts b/object/is-object.bench.ts new file mode 100644 index 0000000..3a2de21 --- /dev/null +++ b/object/is-object.bench.ts @@ -0,0 +1,52 @@ +/** + * Benchmark for isObject function + */ + +import { isObject } from './is-object.ts' + +// Test data +const plainObject = { key: 'value' } +const array = [1, 2, 3] +const nullValue = null +const undefinedValue = undefined +const number = 42 +const string = 'hello' +const date = new Date() +const emptyObject = {} +const nestedObject = { a: { b: { c: 'value' } } } + +Deno.bench('isObject - plain object', () => { + isObject(plainObject) +}) + +Deno.bench('isObject - array', () => { + isObject(array) +}) + +Deno.bench('isObject - null', () => { + isObject(nullValue) +}) + +Deno.bench('isObject - undefined', () => { + isObject(undefinedValue) +}) + +Deno.bench('isObject - number', () => { + isObject(number) +}) + +Deno.bench('isObject - string', () => { + isObject(string) +}) + +Deno.bench('isObject - Date', () => { + isObject(date) +}) + +Deno.bench('isObject - empty object', () => { + isObject(emptyObject) +}) + +Deno.bench('isObject - nested object', () => { + isObject(nestedObject) +}) diff --git a/object/omit.bench.ts b/object/omit.bench.ts new file mode 100644 index 0000000..48a49bb --- /dev/null +++ b/object/omit.bench.ts @@ -0,0 +1,54 @@ +/** + * Benchmark for omit function + */ + +import { omit } from './omit.ts' + +// Test data +const smallObject = { + id: 1, + name: 'Alice', + email: 'alice@example.com', + age: 30, + active: true, + password: 'secret', +} + +const mediumObject = Object.fromEntries( + Array.from({ length: 50 }, (_, i) => [`key${i}`, `value${i}`]), +) + +const largeObject = Object.fromEntries( + Array.from({ length: 200 }, (_, i) => [`key${i}`, `value${i}`]), +) + +Deno.bench('omit - small object, 1 key', () => { + omit(smallObject, ['password']) +}) + +Deno.bench('omit - small object, 2 keys', () => { + omit(smallObject, ['password', 'email']) +}) + +Deno.bench('omit - medium object (50 keys), omit 5', () => { + omit(mediumObject, ['key1', 'key10', 'key20', 'key30', 'key40']) +}) + +Deno.bench('omit - large object (200 keys), omit 10', () => { + omit(largeObject, [ + 'key1', + 'key20', + 'key40', + 'key60', + 'key80', + 'key100', + 'key120', + 'key140', + 'key160', + 'key180', + ]) +}) + +Deno.bench('omit - empty keys array', () => { + omit(smallObject, []) +}) diff --git a/object/pick.bench.ts b/object/pick.bench.ts new file mode 100644 index 0000000..3d4d5ad --- /dev/null +++ b/object/pick.bench.ts @@ -0,0 +1,53 @@ +/** + * Benchmark for pick function + */ + +import { pick } from './pick.ts' + +// Test data +const smallObject = { + id: 1, + name: 'Alice', + email: 'alice@example.com', + age: 30, + active: true, +} + +const mediumObject = Object.fromEntries( + Array.from({ length: 50 }, (_, i) => [`key${i}`, `value${i}`]), +) + +const largeObject = Object.fromEntries( + Array.from({ length: 200 }, (_, i) => [`key${i}`, `value${i}`]), +) + +Deno.bench('pick - small object, 2 keys', () => { + pick(smallObject, ['id', 'name']) +}) + +Deno.bench('pick - small object, most keys', () => { + pick(smallObject, ['id', 'name', 'email', 'age']) +}) + +Deno.bench('pick - medium object (50 keys), pick 5', () => { + pick(mediumObject, ['key1', 'key10', 'key20', 'key30', 'key40']) +}) + +Deno.bench('pick - large object (200 keys), pick 10', () => { + pick(largeObject, [ + 'key1', + 'key20', + 'key40', + 'key60', + 'key80', + 'key100', + 'key120', + 'key140', + 'key160', + 'key180', + ]) +}) + +Deno.bench('pick - empty keys array', () => { + pick(smallObject, []) +}) diff --git a/text/remove-accents.bench.ts b/text/remove-accents.bench.ts new file mode 100644 index 0000000..7f81f03 --- /dev/null +++ b/text/remove-accents.bench.ts @@ -0,0 +1,37 @@ +/** + * Benchmark for removeAccents function + */ + +import { removeAccents } from './remove-accents.ts' + +// Test data +const simple = 'hello world' +const withAccents = 'hĂ©llo wörld çafĂ©' +const manyAccents = 'Ă Ă©ĂȘĂ­ĂłĂșĂ±Ă§Ă„Ă€Ă¶ĂŒ' +const longText = + 'ThĂ© qĂŒĂ­ck bröwn fĂłx jĂșmps övĂ©r thĂ© lĂ€zy dög. HĂ©llo Wörld! ÇafĂ©, naĂŻve, rĂ©sumĂ©.' +const mixed = 'Some normal text with Ă ccĂ©nts and sĂ­mböls!' + +Deno.bench('removeAccents - simple text (no accents)', () => { + removeAccents(simple) +}) + +Deno.bench('removeAccents - text with few accents', () => { + removeAccents(withAccents) +}) + +Deno.bench('removeAccents - text with many accents', () => { + removeAccents(manyAccents) +}) + +Deno.bench('removeAccents - long text with accents', () => { + removeAccents(longText) +}) + +Deno.bench('removeAccents - mixed content', () => { + removeAccents(mixed) +}) + +Deno.bench('removeAccents - empty string', () => { + removeAccents('') +}) diff --git a/text/string-includes.bench.ts b/text/string-includes.bench.ts new file mode 100644 index 0000000..881c1ec --- /dev/null +++ b/text/string-includes.bench.ts @@ -0,0 +1,38 @@ +/** + * Benchmark for stringIncludes function + */ + +import { stringIncludes } from './string-includes.ts' + +// Test data +const text = 'Hello World, this is a test string' +const longText = + 'This is a much longer string with many words to search through for performance testing' + +Deno.bench('stringIncludes - found at start', () => { + stringIncludes(text, 'hello') +}) + +Deno.bench('stringIncludes - found in middle', () => { + stringIncludes(text, 'world') +}) + +Deno.bench('stringIncludes - found at end', () => { + stringIncludes(text, 'string') +}) + +Deno.bench('stringIncludes - not found', () => { + stringIncludes(text, 'notfound') +}) + +Deno.bench('stringIncludes - long text search', () => { + stringIncludes(longText, 'performance') +}) + +Deno.bench('stringIncludes - exact case match', () => { + stringIncludes(text, 'Hello') +}) + +Deno.bench('stringIncludes - empty search', () => { + stringIncludes(text, '') +}) diff --git a/text/string-to-boolean.bench.ts b/text/string-to-boolean.bench.ts new file mode 100644 index 0000000..0cf834d --- /dev/null +++ b/text/string-to-boolean.bench.ts @@ -0,0 +1,45 @@ +/** + * Benchmark for stringToBoolean function + */ + +import { stringToBoolean } from './string-to-boolean.ts' + +Deno.bench('stringToBoolean - "true"', () => { + stringToBoolean('true') +}) + +Deno.bench('stringToBoolean - "false"', () => { + stringToBoolean('false') +}) + +Deno.bench('stringToBoolean - "TRUE"', () => { + stringToBoolean('TRUE') +}) + +Deno.bench('stringToBoolean - "FALSE"', () => { + stringToBoolean('FALSE') +}) + +Deno.bench('stringToBoolean - "1"', () => { + stringToBoolean('1') +}) + +Deno.bench('stringToBoolean - "0"', () => { + stringToBoolean('0') +}) + +Deno.bench('stringToBoolean - "yes"', () => { + stringToBoolean('yes') +}) + +Deno.bench('stringToBoolean - "no"', () => { + stringToBoolean('no') +}) + +Deno.bench('stringToBoolean - invalid string', () => { + stringToBoolean('invalid') +}) + +Deno.bench('stringToBoolean - empty string', () => { + stringToBoolean('') +}) From dda16a26207b964c35da5c10c8a9fcfd706178fa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 15 Nov 2025 08:34:07 +0000 Subject: [PATCH 4/6] Add benchmark history directory with example and documentation Co-authored-by: edouardmisset <63284636+edouardmisset@users.noreply.github.com> --- .benchmark-history/README.md | 24 ++++++++++++++++ .benchmark-history/history.example.json | 38 +++++++++++++++++++++++++ .gitignore | 2 +- function/memoization.bench.ts | 4 +-- 4 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 .benchmark-history/README.md create mode 100644 .benchmark-history/history.example.json diff --git a/.benchmark-history/README.md b/.benchmark-history/README.md new file mode 100644 index 0000000..c0c5948 --- /dev/null +++ b/.benchmark-history/README.md @@ -0,0 +1,24 @@ +# Benchmark History + +This directory stores the benchmark history in `history.json`. The file is +gitignored. + +## Format + +See `history.example.json` for the format. Each entry contains: + +- Timestamp of the benchmark run +- System information (Deno, V8, TypeScript versions) +- Hardware details (OS, architecture, CPU cores) +- Package versions for each workspace +- Benchmark results for each function + +## Usage + +History is automatically managed by the benchmark runner: + +```bash +deno task bench +``` + +The history file keeps the last 100 benchmark runs. diff --git a/.benchmark-history/history.example.json b/.benchmark-history/history.example.json new file mode 100644 index 0000000..5a979ff --- /dev/null +++ b/.benchmark-history/history.example.json @@ -0,0 +1,38 @@ +[ + { + "timestamp": "2024-01-15T10:00:00.000Z", + "denoVersion": "2.5.6", + "v8Version": "14.0.365.5", + "typescriptVersion": "5.9.2", + "os": "linux", + "arch": "x86_64", + "cpuModel": "Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz", + "cpuCores": 4, + "memoryTotal": 0, + "packages": { + "array": "5.0.0", + "date": "5.0.0", + "function": "5.0.0", + "math": "5.0.0", + "object": "5.0.0", + "text": "5.0.0", + "type": "5.0.0" + }, + "benchmarks": { + "text/capitalize": [ + { + "name": "capitalize - short string", + "measuredRunsAvgMs": 0.0000447, + "measuredRunsMs": [], + "totalMs": 50.2, + "iterationsPerSec": 22371364, + "min": 0.0000446, + "max": 0.0001284, + "p75": 0.0000472, + "p99": 0.0000583, + "p995": 0.0000899 + } + ] + } + } +] diff --git a/.gitignore b/.gitignore index 407a881..385929f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ coverage docs .DS_Store -.benchmark-history +.benchmark-history/history.json diff --git a/function/memoization.bench.ts b/function/memoization.bench.ts index 2b1449c..887d98f 100644 --- a/function/memoization.bench.ts +++ b/function/memoization.bench.ts @@ -5,9 +5,9 @@ import { memoization } from './memoization.ts' // Test functions to memoize -const fibonacci = (n: number): number => { +const _fibonacci = (n: number): number => { if (n <= 1) return n - return fibonacci(n - 1) + fibonacci(n - 2) + return _fibonacci(n - 1) + _fibonacci(n - 2) } const add = (a: number, b: number): number => a + b From 7432be8611e122ebd692ada5ef03e3248d4969b0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 15 Nov 2025 09:19:05 +0000 Subject: [PATCH 5/6] Fix benchmark runner to parse Deno bench JSON output correctly Co-authored-by: edouardmisset <63284636+edouardmisset@users.noreply.github.com> --- _internal-tools/run-benchmarks.ts | 53 +++++++++++++++++++------------ 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/_internal-tools/run-benchmarks.ts b/_internal-tools/run-benchmarks.ts index 33e006b..572ddb2 100644 --- a/_internal-tools/run-benchmarks.ts +++ b/_internal-tools/run-benchmarks.ts @@ -138,32 +138,43 @@ async function runWorkspaceBenchmarks( if (output.success) { const text = new TextDecoder().decode(output.stdout) - const lines = text.split('\n').filter((line) => line.trim()) const benchResults: BenchmarkResult[] = [] - for (const line of lines) { - try { - const json = JSON.parse(line) - if (json.ok && json.result) { - benchResults.push({ - name: json.name || functionName, - measuredRunsAvgMs: json.result.measuredRunsAvgMs || 0, - measuredRunsMs: json.result.measuredRunsMs || [], - totalMs: json.result.totalMs || 0, - iterationsPerSec: json.result.measuredRunsAvgMs - ? 1000 / json.result.measuredRunsAvgMs - : 0, - min: json.result.min || 0, - max: json.result.max || 0, - p75: json.result.p75 || 0, - p99: json.result.p99 || 0, - p995: json.result.p995 || 0, - }) + try { + const json = JSON.parse(text) + if (json.benches && Array.isArray(json.benches)) { + for (const bench of json.benches) { + if ( + bench.results && Array.isArray(bench.results) && + bench.results.length > 0 + ) { + const result = bench.results[0] + if (result.ok) { + const okData = result.ok + // Convert nanoseconds to milliseconds + const avgMs = okData.avg / 1_000_000 + benchResults.push({ + name: bench.name || functionName, + measuredRunsAvgMs: avgMs, + measuredRunsMs: [], + totalMs: (okData.n * okData.avg) / 1_000_000, + iterationsPerSec: avgMs ? 1000 / avgMs : 0, + min: okData.min / 1_000_000, + max: okData.max / 1_000_000, + p75: okData.p75 / 1_000_000, + p99: okData.p99 / 1_000_000, + p995: okData.p995 / 1_000_000, + }) + } + } } - } catch { - // Skip invalid JSON lines } + } catch (error) { + console.error( + `Error parsing benchmark output for ${benchFile}:`, + error, + ) } if (benchResults.length > 0) { From ba570a9e8a94728fbe15de0de59873d700d2130f Mon Sep 17 00:00:00 2001 From: Edouard Misset Date: Sat, 15 Nov 2025 10:28:49 +0100 Subject: [PATCH 6/6] chore: remove duplicate task --- deno.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/deno.json b/deno.json index d826656..110d471 100644 --- a/deno.json +++ b/deno.json @@ -2,8 +2,7 @@ "$schema": "https://raw.githubusercontent.com/denoland/deno/main/cli/schemas/config-file.v1.json", "tasks": { "bench:generate": "deno run --allow-read --allow-write ./_internal-tools/generate-benchmarks.ts", - "bench:run": "deno run --allow-read --allow-write --allow-run ./_internal-tools/run-benchmarks.ts", - "bench": "deno task bench:run", + "bench": "deno run --allow-read --allow-write --allow-run ./_internal-tools/run-benchmarks.ts", "check:exports": "deno run --allow-read --allow-write --allow-run ./_internal-tools/check-exports.ts", "check:imports": "deno run --allow-read ./_internal-tools/check-imports.ts", "check:links": "deno run --allow-read ./_internal-tools/check-links.ts", @@ -86,4 +85,4 @@ "_internal-tools" ] } -} +} \ No newline at end of file