Prerequisites
- Basic understanding of JavaScript ๐
- TypeScript installation โก
- VS Code or preferred IDE ๐ป
What you'll learn
- Understand the concept fundamentals ๐ฏ
- Apply the concept in real projects ๐๏ธ
- Debug common issues ๐
- Write type-safe code โจ
๐ฏ Introduction
Welcome to this exciting tutorial on benchmark creation and performance testing in TypeScript! ๐ In this guide, weโll explore how to measure and optimize your codeโs performance like a pro.
Youโll discover how creating effective benchmarks can transform your TypeScript applications from slow and sluggish to fast and efficient. Whether youโre building web applications ๐, server-side code ๐ฅ๏ธ, or libraries ๐, understanding performance testing is essential for delivering blazing-fast user experiences.
By the end of this tutorial, youโll feel confident creating benchmarks that reveal performance bottlenecks and guide your optimization efforts! Letโs dive in! ๐โโ๏ธ
๐ Understanding Benchmark Creation
๐ค What is Performance Benchmarking?
Performance benchmarking is like a race track for your code ๐. Think of it as timing different runners (code implementations) to see who crosses the finish line fastest!
In TypeScript terms, benchmarking helps you measure how long operations take, how much memory they use, and how they perform under different conditions. This means you can:
- โจ Identify slow code before users complain
- ๐ Compare different implementation approaches
- ๐ก๏ธ Prevent performance regressions
๐ก Why Use Benchmarks?
Hereโs why developers love performance benchmarking:
- Data-Driven Decisions ๐: No more guessing which code is faster
- Objective Measurements ๐ป: Real numbers instead of feelings
- Performance Tracking ๐: Monitor speed over time
- Optimization Confidence ๐ง: Know your changes actually help
Real-world example: Imagine building an e-commerce search ๐. With benchmarks, you can ensure searches stay fast even with millions of products!
๐ง Basic Syntax and Usage
๐ Simple Example
Letโs start with a friendly example:
// ๐ Hello, Performance Testing!
interface BenchmarkResult {
name: string; // ๐ท๏ธ Test name
duration: number; // โฑ๏ธ Time in milliseconds
ops: number; // ๐ Operations per second
}
// ๐จ Creating a simple benchmark
class SimpleBenchmark {
// โฑ๏ธ Measure execution time
static measure(name: string, fn: () => void, iterations = 1000): BenchmarkResult {
const start = performance.now();
// ๐ Run the function multiple times
for (let i = 0; i < iterations; i++) {
fn();
}
const end = performance.now();
const duration = end - start;
const ops = Math.round((iterations / duration) * 1000);
console.log(`โจ ${name}: ${duration.toFixed(2)}ms for ${iterations} runs`);
console.log(`๐ ${ops} operations per second`);
return { name, duration, ops };
}
}
๐ก Explanation: Notice how we run the function multiple times to get accurate measurements! Single runs can be misleading due to system variability.
๐ฏ Common Patterns
Here are patterns youโll use daily:
// ๐๏ธ Pattern 1: Comparing implementations
const compareArrayMethods = () => {
const numbers = Array.from({ length: 10000 }, (_, i) => i);
// ๐จ Test 1: Traditional for loop
SimpleBenchmark.measure("For Loop", () => {
let sum = 0;
for (let i = 0; i < numbers.length; i++) {
sum += numbers[i];
}
});
// ๐จ Test 2: Array reduce
SimpleBenchmark.measure("Array Reduce", () => {
numbers.reduce((sum, n) => sum + n, 0);
});
};
// ๐ Pattern 2: Memory benchmarking
interface MemorySnapshot {
heapUsed: number; // ๐พ Memory in bytes
external: number; // ๐ฆ External memory
}
// ๐ฏ Pattern 3: Async benchmarking
async function benchmarkAsync(
name: string,
fn: () => Promise<void>,
iterations = 100
): Promise<BenchmarkResult> {
const start = performance.now();
for (let i = 0; i < iterations; i++) {
await fn();
}
const duration = performance.now() - start;
return { name, duration, ops: (iterations / duration) * 1000 };
}
๐ก Practical Examples
๐ Example 1: E-Commerce Search Optimization
Letโs benchmark a real product search:
// ๐๏ธ Define our product type
interface Product {
id: string;
name: string;
price: number;
category: string;
tags: string[];
emoji: string; // Every product needs an emoji!
}
// ๐ Search implementation to benchmark
class ProductSearch {
private products: Product[] = [];
private indexMap: Map<string, Set<Product>> = new Map();
constructor(products: Product[]) {
this.products = products;
this.buildIndex();
}
// ๐ Build search index
private buildIndex(): void {
this.products.forEach(product => {
// Index by name words
const words = product.name.toLowerCase().split(' ');
words.forEach(word => {
if (!this.indexMap.has(word)) {
this.indexMap.set(word, new Set());
}
this.indexMap.get(word)!.add(product);
});
});
}
// ๐ Slow search (for comparison)
searchSlow(query: string): Product[] {
const lowerQuery = query.toLowerCase();
return this.products.filter(p =>
p.name.toLowerCase().includes(lowerQuery)
);
}
// ๐ Fast indexed search
searchFast(query: string): Product[] {
const lowerQuery = query.toLowerCase();
const words = lowerQuery.split(' ');
const results = new Set<Product>();
words.forEach(word => {
const matches = this.indexMap.get(word) || new Set();
matches.forEach(product => results.add(product));
});
return Array.from(results);
}
}
// ๐ฎ Let's benchmark it!
const runSearchBenchmark = () => {
// ๐๏ธ Create test data
const products: Product[] = Array.from({ length: 10000 }, (_, i) => ({
id: `prod-${i}`,
name: `Product ${i} ${['Awesome', 'Super', 'Ultra'][i % 3]}`,
price: Math.random() * 100,
category: ['Electronics', 'Books', 'Clothing'][i % 3],
tags: ['new', 'sale', 'popular'].slice(0, (i % 3) + 1),
emoji: ['๐ฑ', '๐', '๐'][i % 3]
}));
const search = new ProductSearch(products);
console.log("๐ Starting search benchmark...\n");
// ๐ Benchmark slow search
SimpleBenchmark.measure("Slow Search", () => {
search.searchSlow("awesome");
}, 1000);
console.log("\n");
// ๐ Benchmark fast search
SimpleBenchmark.measure("Fast Search", () => {
search.searchFast("awesome");
}, 1000);
};
๐ฏ Try it yourself: Add a fuzzy search method and benchmark it against the others!
๐ฎ Example 2: Game Engine Performance
Letโs benchmark a game update loop:
// ๐ Game entity system benchmark
interface GameObject {
id: string;
x: number;
y: number;
velocity: { x: number; y: number };
emoji: string;
}
class GameEngine {
private objects: GameObject[] = [];
private spatialGrid: Map<string, GameObject[]> = new Map();
private gridSize = 100;
// ๐ฎ Add game objects
addObjects(count: number): void {
for (let i = 0; i < count; i++) {
this.objects.push({
id: `obj-${i}`,
x: Math.random() * 1000,
y: Math.random() * 1000,
velocity: {
x: (Math.random() - 0.5) * 10,
y: (Math.random() - 0.5) * 10
},
emoji: ['๐', '๐ธ', 'โญ', '๐'][i % 4]
});
}
this.updateSpatialGrid();
}
// ๐บ๏ธ Update spatial partitioning
private updateSpatialGrid(): void {
this.spatialGrid.clear();
this.objects.forEach(obj => {
const gridKey = this.getGridKey(obj.x, obj.y);
if (!this.spatialGrid.has(gridKey)) {
this.spatialGrid.set(gridKey, []);
}
this.spatialGrid.get(gridKey)!.push(obj);
});
}
// ๐ Get grid cell key
private getGridKey(x: number, y: number): string {
const gridX = Math.floor(x / this.gridSize);
const gridY = Math.floor(y / this.gridSize);
return `${gridX},${gridY}`;
}
// ๐ Update without optimization
updateSlow(deltaTime: number): void {
// Update positions
this.objects.forEach(obj => {
obj.x += obj.velocity.x * deltaTime;
obj.y += obj.velocity.y * deltaTime;
});
// Check all collisions (nยฒ)
for (let i = 0; i < this.objects.length; i++) {
for (let j = i + 1; j < this.objects.length; j++) {
const distance = Math.sqrt(
Math.pow(this.objects[i].x - this.objects[j].x, 2) +
Math.pow(this.objects[i].y - this.objects[j].y, 2)
);
if (distance < 50) {
// Collision detected! ๐ฅ
}
}
}
}
// ๐ Update with spatial optimization
updateFast(deltaTime: number): void {
// Update positions
this.objects.forEach(obj => {
obj.x += obj.velocity.x * deltaTime;
obj.y += obj.velocity.y * deltaTime;
});
// Update grid
this.updateSpatialGrid();
// Check collisions only in nearby cells
this.spatialGrid.forEach((cellObjects) => {
for (let i = 0; i < cellObjects.length; i++) {
for (let j = i + 1; j < cellObjects.length; j++) {
const distance = Math.sqrt(
Math.pow(cellObjects[i].x - cellObjects[j].x, 2) +
Math.pow(cellObjects[i].y - cellObjects[j].y, 2)
);
if (distance < 50) {
// Collision detected! ๐ฅ
}
}
}
});
}
}
// ๐ฏ Benchmark the game engine
const benchmarkGameEngine = () => {
const engine = new GameEngine();
engine.addObjects(1000); // 1000 game objects!
console.log("๐ฎ Game Engine Benchmark\n");
SimpleBenchmark.measure("Slow Update", () => {
engine.updateSlow(0.016); // 60 FPS
}, 100);
console.log("\n");
SimpleBenchmark.measure("Fast Update", () => {
engine.updateFast(0.016);
}, 100);
};
๐ Advanced Concepts
๐งโโ๏ธ Advanced Topic 1: Statistical Benchmarking
When youโre ready to level up, try this advanced pattern:
// ๐ฏ Advanced benchmark with statistics
interface DetailedBenchmarkResult {
name: string;
runs: number[];
mean: number;
median: number;
stdDev: number;
percentile95: number;
sparkles: "โจ" | "๐" | "๐ซ";
}
class AdvancedBenchmark {
// ๐ช Run benchmark with statistics
static async measureDetailed(
name: string,
fn: () => void | Promise<void>,
options = { warmupRuns: 10, testRuns: 100 }
): Promise<DetailedBenchmarkResult> {
const runs: number[] = [];
// ๐ฅ Warmup runs
console.log(`๐ฅ Warming up ${name}...`);
for (let i = 0; i < options.warmupRuns; i++) {
await fn();
}
// ๐ Test runs
console.log(`๐ Testing ${name}...`);
for (let i = 0; i < options.testRuns; i++) {
const start = performance.now();
await fn();
const duration = performance.now() - start;
runs.push(duration);
}
// ๐ Calculate statistics
const sorted = [...runs].sort((a, b) => a - b);
const mean = runs.reduce((a, b) => a + b) / runs.length;
const median = sorted[Math.floor(sorted.length / 2)];
const variance = runs.reduce((sum, run) =>
sum + Math.pow(run - mean, 2), 0) / runs.length;
const stdDev = Math.sqrt(variance);
const percentile95 = sorted[Math.floor(sorted.length * 0.95)];
const sparkles = stdDev < mean * 0.1 ? "๐ซ" :
stdDev < mean * 0.2 ? "๐" : "โจ";
return { name, runs, mean, median, stdDev, percentile95, sparkles };
}
}
๐๏ธ Advanced Topic 2: Memory Profiling
For the brave developers:
// ๐ Memory benchmark utilities
class MemoryBenchmark {
private initialMemory: number = 0;
// ๐พ Start memory tracking
start(): void {
if (global.gc) {
global.gc(); // Force garbage collection
}
this.initialMemory = process.memoryUsage().heapUsed;
}
// ๐ Get memory delta
getMemoryDelta(): number {
const currentMemory = process.memoryUsage().heapUsed;
return currentMemory - this.initialMemory;
}
// ๐ฏ Measure memory allocation
static measureMemory<T>(
name: string,
fn: () => T
): { result: T; memoryUsed: number } {
const bench = new MemoryBenchmark();
bench.start();
const result = fn();
const memoryUsed = bench.getMemoryDelta();
const memoryMB = (memoryUsed / 1024 / 1024).toFixed(2);
console.log(`๐พ ${name}: ${memoryMB} MB allocated`);
return { result, memoryUsed };
}
}
โ ๏ธ Common Pitfalls and Solutions
๐ฑ Pitfall 1: Micro-benchmark Bias
// โ Wrong way - too small to measure accurately!
const badBenchmark = () => {
const start = performance.now();
const result = 1 + 1;
const end = performance.now();
console.log(`Time: ${end - start}ms`); // ๐ฅ Often 0ms!
};
// โ
Correct way - run multiple iterations!
const goodBenchmark = () => {
const iterations = 1000000;
const start = performance.now();
for (let i = 0; i < iterations; i++) {
const result = 1 + 1;
}
const end = performance.now();
const avgTime = (end - start) / iterations;
console.log(`โ
Average time: ${avgTime * 1000000}ns`);
};
๐คฏ Pitfall 2: Ignoring JIT Optimization
// โ Dangerous - first runs are slow!
function measureOnce(fn: () => void): number {
const start = performance.now();
fn();
return performance.now() - start;
}
// โ
Safe - warm up the JIT!
function measureWithWarmup(fn: () => void): number {
// ๐ฅ Warm up runs
for (let i = 0; i < 100; i++) {
fn();
}
// ๐ Actual measurement
const start = performance.now();
for (let i = 0; i < 1000; i++) {
fn();
}
return (performance.now() - start) / 1000;
}
๐ ๏ธ Best Practices
- ๐ฏ Measure Real Scenarios: Donโt benchmark trivial operations
- ๐ Use Multiple Runs: Single measurements are unreliable
- ๐ก๏ธ Control Environment: Close other apps during benchmarks
- ๐จ Compare Apples to Apples: Same data, same conditions
- โจ Document Results: Track performance over time
๐งช Hands-On Exercise
๐ฏ Challenge: Build a String Processing Benchmark Suite
Create a comprehensive benchmark for string operations:
๐ Requirements:
- โ Compare different string concatenation methods
- ๐ท๏ธ Test string search algorithms (indexOf vs regex)
- ๐ค Measure string transformation performance
- ๐ Include both sync and async operations
- ๐จ Create visual performance reports!
๐ Bonus Points:
- Add memory usage tracking
- Implement result caching benchmarks
- Create a CLI tool for running benchmarks
๐ก Solution
๐ Click to see solution
// ๐ฏ String Processing Benchmark Suite!
interface StringBenchmarkResult {
method: string;
duration: number;
memoryUsed?: number;
opsPerSecond: number;
emoji: string;
}
class StringBenchmarkSuite {
private results: StringBenchmarkResult[] = [];
// ๐ Run all benchmarks
async runAll(): Promise<void> {
console.log("๐ String Processing Benchmark Suite\n");
await this.benchmarkConcatenation();
await this.benchmarkSearch();
await this.benchmarkTransformation();
this.printReport();
}
// ๐ Benchmark concatenation methods
private async benchmarkConcatenation(): Promise<void> {
console.log("๐ Testing String Concatenation...\n");
const strings = Array.from({ length: 1000 }, (_, i) => `string${i}`);
// Method 1: Plus operator
const plusResult = await this.measure("Plus Operator", () => {
let result = "";
for (const str of strings) {
result = result + str;
}
});
// Method 2: Array join
const joinResult = await this.measure("Array Join", () => {
strings.join("");
});
// Method 3: Template literals
const templateResult = await this.measure("Template Literals", () => {
let result = "";
for (const str of strings) {
result = `${result}${str}`;
}
});
this.results.push(plusResult, joinResult, templateResult);
}
// ๐ Benchmark search methods
private async benchmarkSearch(): Promise<void> {
console.log("\n๐ Testing String Search...\n");
const text = "The quick brown fox jumps over the lazy dog".repeat(1000);
const searchTerm = "fox";
// Method 1: indexOf
const indexOfResult = await this.measure("indexOf", () => {
let count = 0;
let index = 0;
while ((index = text.indexOf(searchTerm, index)) !== -1) {
count++;
index += searchTerm.length;
}
});
// Method 2: Regular Expression
const regexResult = await this.measure("RegExp", () => {
const matches = text.match(new RegExp(searchTerm, 'g'));
const count = matches ? matches.length : 0;
});
this.results.push(indexOfResult, regexResult);
}
// ๐จ Benchmark transformations
private async benchmarkTransformation(): Promise<void> {
console.log("\n๐จ Testing String Transformations...\n");
const text = "Hello World This Is A Test".repeat(100);
// Method 1: toLowerCase
const lowerResult = await this.measure("toLowerCase", () => {
text.toLowerCase();
});
// Method 2: Replace
const replaceResult = await this.measure("Replace Spaces", () => {
text.replace(/ /g, '_');
});
// Method 3: Split and Join
const splitJoinResult = await this.measure("Split & Join", () => {
text.split(' ').map(word => word.toLowerCase()).join('-');
});
this.results.push(lowerResult, replaceResult, splitJoinResult);
}
// โฑ๏ธ Measure a single operation
private async measure(
method: string,
fn: () => void
): Promise<StringBenchmarkResult> {
const warmupRuns = 100;
const testRuns = 1000;
// ๐ฅ Warmup
for (let i = 0; i < warmupRuns; i++) {
fn();
}
// ๐ Test
const start = performance.now();
for (let i = 0; i < testRuns; i++) {
fn();
}
const duration = performance.now() - start;
const opsPerSecond = Math.round((testRuns / duration) * 1000);
const emoji = opsPerSecond > 10000 ? "๐" :
opsPerSecond > 5000 ? "โก" :
opsPerSecond > 1000 ? "โจ" : "๐";
console.log(`${emoji} ${method}: ${duration.toFixed(2)}ms for ${testRuns} runs`);
console.log(` ${opsPerSecond} ops/sec\n`);
return { method, duration, opsPerSecond, emoji };
}
// ๐ Print final report
private printReport(): void {
console.log("\n๐ === BENCHMARK REPORT === ๐\n");
// Sort by performance
const sorted = [...this.results].sort((a, b) =>
b.opsPerSecond - a.opsPerSecond
);
sorted.forEach((result, index) => {
const medal = index === 0 ? "๐ฅ" :
index === 1 ? "๐ฅ" :
index === 2 ? "๐ฅ" : " ";
console.log(`${medal} ${result.emoji} ${result.method}`);
console.log(` Performance: ${result.opsPerSecond} ops/sec`);
console.log(` Duration: ${result.duration.toFixed(2)}ms\n`);
});
console.log("โจ Benchmark complete! โจ");
}
}
// ๐ฎ Run the benchmark!
const runStringBenchmarks = async () => {
const suite = new StringBenchmarkSuite();
await suite.runAll();
};
๐ Key Takeaways
Youโve learned so much! Hereโs what you can now do:
- โ Create benchmarks with confidence ๐ช
- โ Avoid common pitfalls that skew results ๐ก๏ธ
- โ Apply best practices for accurate measurements ๐ฏ
- โ Debug performance issues like a pro ๐
- โ Build faster applications with TypeScript! ๐
Remember: Performance matters, but measure first before optimizing! Donโt guess, test! ๐ค
๐ค Next Steps
Congratulations! ๐ Youโve mastered performance benchmarking!
Hereโs what to do next:
- ๐ป Practice with the exercises above
- ๐๏ธ Benchmark your own projects
- ๐ Move on to our next tutorial: Profile-guided Optimization
- ๐ Share your benchmark results with the team!
Remember: Every millisecond saved makes users happier. Keep measuring, keep optimizing, and most importantly, have fun! ๐
Happy benchmarking! ๐๐โจ