Back to patterns

Performance Bottleneck

PERFORMANCE

A systematic method for identifying and resolving performance bottlenecks through waterfall diagrams, load time analysis, and resource optimization.

Diagram for Performance Bottleneck

Use Cases

  • Load time optimization
  • Resource utilization
  • Performance monitoring
  • Bottleneck identification

Production Implementation

Implementation
// Real-world example: Performance monitoring and optimization toolkit
interface PerformanceMetrics {
  fps: number;
  memory: {
    used: number;
    total: number;
  };
  timing: {
    [key: string]: number;
  };
  resources: Array<{
    name: string;
    size: number;
    loadTime: number;
  }>;
}

interface ProfilerOptions {
  sampleInterval?: number;
  maxDataPoints?: number;
  enableMemoryProfiling?: boolean;
  logLevel?: 'debug' | 'info' | 'warn' | 'error';
}

class PerformanceProfiler {
  private metrics: PerformanceMetrics[];
  private sampleInterval: number;
  private maxDataPoints: number;
  private isRunning: boolean;
  private intervalId?: number;
  private marks: Map<string, number>;
  private resourceObserver?: PerformanceObserver;
  private logger: Console;

  constructor(options: ProfilerOptions = {}) {
    this.metrics = [];
    this.sampleInterval = options.sampleInterval || 1000;
    this.maxDataPoints = options.maxDataPoints || 100;
    this.isRunning = false;
    this.marks = new Map();
    this.logger = console;

    // Initialize performance observers
    this.setupObservers();
  }

  private setupObservers(): void {
    // Resource timing observer
    try {
      this.resourceObserver = new PerformanceObserver((list) => {
        list.getEntries().forEach(entry => {
          if (entry.entryType === 'resource') {
            this.logResourceTiming(entry as PerformanceResourceTiming);
          }
        });
      });

      this.resourceObserver.observe({
        entryTypes: ['resource']
      });

    } catch (error) {
      this.logger.warn('Resource timing API not supported:', error);
    }
  }

  start(): void {
    if (this.isRunning) return;
    
    this.isRunning = true;
    this.collectMetrics();
    
    this.intervalId = window.setInterval(
      () => this.collectMetrics(),
      this.sampleInterval
    );
  }

  stop(): void {
    if (!this.isRunning) return;
    
    this.isRunning = false;
    if (this.intervalId) {
      clearInterval(this.intervalId);
    }
  }

  mark(name: string): void {
    this.marks.set(name, performance.now());
  }

  measure(name: string, startMark: string): number {
    const start = this.marks.get(startMark);
    if (!start) {
      throw new Error(`Start mark "${startMark}" not found`);
    }

    const duration = performance.now() - start;
    this.logger.debug(`Measurement "${name}": ${duration.toFixed(2)}ms`);
    return duration;
  }

  private async collectMetrics(): Promise<void> {
    try {
      const metrics: PerformanceMetrics = {
        fps: await this.measureFPS(),
        memory: this.getMemoryUsage(),
        timing: this.getPageTiming(),
        resources: this.getResourceMetrics()
      };

      this.metrics.push(metrics);

      // Maintain max data points
      if (this.metrics.length > this.maxDataPoints) {
        this.metrics.shift();
      }

      this.analyzeMetrics(metrics);

    } catch (error) {
      this.logger.error('Failed to collect metrics:', error);
    }
  }

  private async measureFPS(): Promise<number> {
    return new Promise(resolve => {
      requestAnimationFrame((t1) => {
        requestAnimationFrame((t2) => {
          resolve(1000 / (t2 - t1));
        });
      });
    });
  }

  private getMemoryUsage(): { used: number; total: number } {
    // Use performance.memory if available (Chrome only)
    const memory = (performance as any).memory;
    if (memory) {
      return {
        used: memory.usedJSHeapSize,
        total: memory.totalJSHeapSize
      };
    }

    // Fallback to rough estimation
    return {
      used: 0,
      total: 0
    };
  }

  private getPageTiming(): { [key: string]: number } {
    const timing = performance.timing;
    return {
      loadTime: timing.loadEventEnd - timing.navigationStart,
      domReady: timing.domContentLoadedEventEnd - timing.navigationStart,
      firstPaint: this.getFirstPaint(),
      ttfb: timing.responseStart - timing.navigationStart
    };
  }

  private getFirstPaint(): number {
    const paint = performance.getEntriesByType('paint')
      .find(entry => entry.name === 'first-paint');
    
    return paint ? paint.startTime : 0;
  }

  private getResourceMetrics(): Array<{
    name: string;
    size: number;
    loadTime: number;
  }> {
    return performance.getEntriesByType('resource')
      .map(entry => ({
        name: entry.name,
        size: (entry as PerformanceResourceTiming).encodedBodySize || 0,
        loadTime: entry.duration
      }));
  }

  private logResourceTiming(entry: PerformanceResourceTiming): void {
    const timing = {
      name: entry.name,
      type: entry.initiatorType,
      size: entry.encodedBodySize,
      duration: entry.duration,
      // Network timing
      dns: entry.domainLookupEnd - entry.domainLookupStart,
      tcp: entry.connectEnd - entry.connectStart,
      ttfb: entry.responseStart - entry.requestStart,
      download: entry.responseEnd - entry.responseStart
    };

    this.logger.debug('Resource Timing:', timing);
  }

  private analyzeMetrics(metrics: PerformanceMetrics): void {
    // FPS analysis
    if (metrics.fps < 30) {
      this.logger.warn('Low FPS detected:', {
        current: metrics.fps,
        threshold: 30
      });
    }

    // Memory analysis
    const memoryUsage = (metrics.memory.used / metrics.memory.total) * 100;
    if (memoryUsage > 80) {
      this.logger.warn('High memory usage:', {
        usage: `${memoryUsage.toFixed(1)}%`,
        threshold: '80%'
      });
    }

    // Load time analysis
    if (metrics.timing.loadTime > 3000) {
      this.logger.warn('Slow page load:', {
        loadTime: `${metrics.timing.loadTime}ms`,
        threshold: '3000ms'
      });
    }

    // Resource analysis
    const largeResources = metrics.resources
      .filter(r => r.size > 1000000)
      .map(r => ({
        name: r.name,
        size: `${(r.size / 1000000).toFixed(1)}MB`
      }));

    if (largeResources.length > 0) {
      this.logger.warn('Large resources detected:', largeResources);
    }
  }

  getMetricsSummary(): {
    averageFPS: number;
    peakMemory: number;
    slowestResources: Array<{ name: string; loadTime: number }>;
  } {
    const averageFPS = this.metrics.reduce(
      (sum, m) => sum + m.fps,
      0
    ) / this.metrics.length;

    const peakMemory = Math.max(
      ...this.metrics.map(m => m.memory.used)
    );

    const allResources = this.metrics
      .flatMap(m => m.resources)
      .sort((a, b) => b.loadTime - a.loadTime)
      .slice(0, 5);

    return {
      averageFPS,
      peakMemory,
      slowestResources: allResources
    };
  }

  generateReport(): string {
    const summary = this.getMetricsSummary();
    
    return `Performance Report
----------------
Average FPS: ${summary.averageFPS.toFixed(1)}
Peak Memory: ${(summary.peakMemory / 1000000).toFixed(1)}MB
Slowest Resources:
${summary.slowestResources
  .map(r => `- ${r.name}: ${r.loadTime.toFixed(0)}ms`)
  .join('\n')
}`;
  }
}

// Example usage: Performance optimization for image loading
class ImageOptimizer {
  private observer: IntersectionObserver;
  private profiler: PerformanceProfiler;

  constructor() {
    this.profiler = new PerformanceProfiler();
    
    // Setup intersection observer for lazy loading
    this.observer = new IntersectionObserver(
      (entries) => this.handleIntersection(entries),
      {
        rootMargin: '50px',
        threshold: 0.1
      }
    );
  }

  optimize(imageElement: HTMLImageElement): void {
    // Start performance monitoring
    this.profiler.mark(`image_start_${imageElement.src}`);

    // Add lazy loading
    imageElement.loading = 'lazy';

    // Observe for viewport entry
    this.observer.observe(imageElement);

    // Add srcset for responsive images
    this.addResponsiveSources(imageElement);
  }

  private handleIntersection(entries: IntersectionObserverEntry[]): void {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        const img = entry.target as HTMLImageElement;
        
        // Load image
        img.src = img.dataset.src || img.src;
        
        // Measure load performance
        img.onload = () => {
          this.profiler.measure(
            `image_load_${img.src}`,
            `image_start_${img.src}`
          );
        };

        // Stop observing
        this.observer.unobserve(img);
      }
    });
  }

  private addResponsiveSources(img: HTMLImageElement): void {
    // Generate srcset for different viewport sizes
    const sizes = [320, 640, 1280, 1920];
    const srcset = sizes
      .map(size => `${this.getOptimizedUrl(img.src, size)} ${size}w`)
      .join(',');

    img.srcset = srcset;
    img.sizes = '(max-width: 320px) 320px, (max-width: 640px) 640px, 1280px';
  }

  private getOptimizedUrl(url: string, width: number): string {
    // Add image optimization service parameters
    return `${url}?width=${width}&quality=80`;
  }
}

// Example usage
const profiler = new PerformanceProfiler({
  sampleInterval: 1000,
  maxDataPoints: 100,
  enableMemoryProfiling: true,
  logLevel: 'warn'
});

// Start monitoring
profiler.start();

// Optimize images
const imageOptimizer = new ImageOptimizer();
document.querySelectorAll('img').forEach(img => {
  imageOptimizer.optimize(img);
});

// Generate report after page load
window.addEventListener('load', () => {
  const report = profiler.generateReport();
  console.log(report);
});

Code Examples

Common Performance Issues

typescript
// ❌ Common performance issues
class DataGrid {
  // Issue 1: Inefficient rendering
  render() {
    this.items.forEach(item => {
      const element = document.createElement('div');
      element.innerHTML = item.toString();
      this.container.appendChild(element);
    });
  }
  
  // Issue 2: Memory leaks
  attachListeners() {
    window.addEventListener('scroll', () => {
      this.handleScroll();
    });
  }
  
  // Issue 3: Blocking operations
  processData(items: any[]) {
    for (const item of items) {
      const result = heavyCalculation(item);
      this.results.push(result);
    }
  }
  
  // Issue 4: Unoptimized images
  loadImages() {
    this.images.forEach(src => {
      const img = new Image();
      img.src = src;
      this.container.appendChild(img);
    });
  }
}

Common performance issues include inefficient DOM operations, memory leaks, blocking operations, and unoptimized resource loading.

Optimized Implementation

typescript
// ✅ Optimized implementation
class DataGrid {
  // Efficient rendering with DocumentFragment
  render() {
    const fragment = document.createDocumentFragment();
    
    requestAnimationFrame(() => {
      this.items.forEach(item => {
        const element = document.createElement('div');
        element.textContent = item.toString();
        fragment.appendChild(element);
      });
      
      this.container.appendChild(fragment);
    });
  }
  
  // Proper event cleanup
  private cleanup: Array<() => void> = [];
  
  attachListeners() {
    const handler = this.handleScroll.bind(this);
    window.addEventListener('scroll', handler);
    this.cleanup.push(() => {
      window.removeEventListener('scroll', handler);
    });
  }
  
  // Non-blocking operations with chunking
  async processData(items: any[]) {
    const chunkSize = 100;
    
    for (let i = 0; i < items.length; i += chunkSize) {
      const chunk = items.slice(i, i + chunkSize);
      
      // Process chunk in next tick
      await new Promise(resolve => setTimeout(resolve, 0));
      
      const results = chunk.map(heavyCalculation);
      this.results.push(...results);
      
      // Update progress
      this.updateProgress(i / items.length);
    }
  }
  
  // Optimized image loading
  loadImages() {
    const imageLoader = new ImageOptimizer();
    
    this.images.forEach(src => {
      const img = new Image();
      
      imageLoader.optimize(img);
      img.dataset.src = src; // For lazy loading
      
      this.container.appendChild(img);
    });
  }
  
  destroy() {
    // Clean up event listeners
    this.cleanup.forEach(cleanup => cleanup());
    this.cleanup = [];
  }
}

This implementation uses efficient DOM operations, proper cleanup, non-blocking operations, and optimized resource loading.

Best Practices

  • Use performance profiling tools
  • Implement efficient DOM operations
  • Optimize resource loading
  • Handle memory management
  • Use non-blocking operations
  • Monitor performance metrics
  • Implement proper cleanup

Common Pitfalls

  • Blocking main thread
  • Memory leaks
  • Unoptimized resource loading
  • Inefficient DOM operations
  • Missing performance monitoring
  • Poor error handling
  • No cleanup implementation