Skip to content

Performance Testing Guide

Audience: Contributors working on expr-eval internals and performance optimization.

This document explains how to run and interpret performance tests for the expr-eval library.

Overview

The performance testing suite includes comprehensive benchmarks for:

  • Parsing Performance - How fast expressions are parsed into AST
  • Evaluation Performance - How fast parsed expressions are evaluated
  • Memory Usage - Memory efficiency and garbage collection impact
  • Scalability - Performance with increasing complexity
  • Regression Detection - Automated performance regression detection

Modern Tooling

The performance tests use modern JavaScript performance testing tools:

  • Tinybench - Lightweight, accurate benchmarking library
  • Vitest - Fast test runner with built-in benchmarking support
  • Performance APIs - Browser and Node.js native performance measurement

Quick Start

Run All Benchmarks

npm run bench

This runs the comprehensive benchmark suite and provides a detailed performance report.

Run Specific Benchmark Categories

# Parsing performance only
npm run bench:parsing

# Evaluation performance only
npm run bench:evaluation

# Memory usage benchmarks
npm run bench:memory

# CI-friendly output (for automated testing)
npm run bench:ci

Benchmark Categories

1. Parsing Performance (benchmarks/parsing.bench.ts)

Tests how efficiently expressions are parsed into Abstract Syntax Trees (AST).

Test Cases: - Simple arithmetic: 2 + 3 - Complex arithmetic: (2 + 3) * 4 - 5 / 2 - Variable expressions: x * y + z - Function calls: sin(x) + cos(y) + sqrt(z) - Nested expressions: ((a + b) * (c - d)) / ((e + f) * (g - h)) - Array operations: [1, 2, 3, 4, 5].map(x => x * 2).filter(x => x > 4) - String operations: "hello" + " " + "world" - Conditional expressions: x > 0 ? y : z

Performance Expectations: - Simple expressions: >100,000 ops/sec - Complex expressions: >10,000 ops/sec - Memory usage should remain stable

2. Evaluation Performance (benchmarks/evaluation.bench.ts)

Tests how efficiently parsed expressions are evaluated with different variable contexts.

Test Scenarios: - Pre-parsed vs parse-and-evaluate - Different variable set sizes - Function-heavy expressions - Mathematical computations - Array and string operations

Performance Expectations: - Simple evaluations: >500,000 ops/sec - Complex evaluations: >50,000 ops/sec - Variable lookup should be O(1)

3. Memory Performance (benchmarks/memory.bench.ts)

Tests memory efficiency and garbage collection impact.

Test Areas: - Parser instance creation/cleanup - Expression reuse vs recreation - Large variable contexts - Deep nested expressions - Long arithmetic expressions

Memory Expectations: - No memory leaks during repeated operations - Efficient garbage collection - Minimal memory footprint per expression

Understanding Results

Reading Benchmark Output

Operation                   Mean Time (ms)    Ops/sec     Status
Simple arithmetic parsing       0.0045        220,000     ✅ Fast
Complex parsing                 0.0250         40,000     ✅ Fast
Very complex parsing            0.1500          6,667     ⚠️ Check

Performance Grades

The benchmark runner assigns overall performance grades:

  • A (🏆): >100,000 avg ops/sec - Excellent performance
  • B (👍): >50,000 avg ops/sec - Good performance
  • C (👌): >10,000 avg ops/sec - Acceptable performance
  • D (⚠️): >1,000 avg ops/sec - Needs optimization
  • F (❌): <1,000 avg ops/sec - Poor performance

Regression Detection

The system automatically flags potential performance regressions:

  • Operations taking >1ms are flagged as potential issues
  • Dramatic performance drops between runs
  • Memory usage increases beyond thresholds

Writing Custom Benchmarks

Using Vitest Bench

import { bench, describe } from 'vitest';
import { Parser } from '../index';

describe('Custom Benchmarks', () => {
  const parser = new Parser();

  bench('My custom test', () => {
    // Your test code here
    parser.evaluate('custom expression');
  });
});

Using Tinybench Directly

import { Bench } from 'tinybench';
import { Parser } from '../index';

const bench = new Bench({ time: 1000 });
const parser = new Parser();

bench.add('Custom test', () => {
  parser.evaluate('custom expression');
});

await bench.run();
console.table(bench.table());

Performance Best Practices

For expr-eval Usage

  1. Reuse Expressions: Parse once, evaluate many times

    const expr = parser.parse('x * y + z');
    // Reuse for different variable values
    expr.evaluate({ x: 1, y: 2, z: 3 });
    expr.evaluate({ x: 4, y: 5, z: 6 });
    

  2. Minimize Variable Context: Only include needed variables

    // Good
    expr.evaluate({ x: 1, y: 2 });
    
    // Avoid if not needed
    expr.evaluate({ x: 1, y: 2, unused1: 3, unused2: 4, ... });
    

  3. Use Simple Expressions: Complex nesting impacts performance

    // Faster
    parser.evaluate('a + b + c');
    
    // Slower
    parser.evaluate('((((a + b) + c) + d) + e)');
    

For expr-eval Development

  1. Profile Before Optimizing: Use benchmarks to identify bottlenecks
  2. Test Edge Cases: Include worst-case scenarios in benchmarks
  3. Monitor Regressions: Run benchmarks in CI/CD pipelines
  4. Memory Awareness: Test for memory leaks and efficient cleanup

Continuous Integration

GitHub Actions Example

name: Performance Tests
on: [push, pull_request]
jobs:
  benchmark:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '18'
      - run: npm ci
      - run: npm run bench:ci
      - name: Check for regressions
        run: |
          # Custom script to compare with baseline
          node scripts/check-performance-regression.js

Performance Monitoring

Set up automated performance monitoring:

  1. Baseline Establishment: Record performance baselines for major versions
  2. Regression Alerts: Automatically flag significant performance drops
  3. Trend Analysis: Track performance trends over time
  4. Environment Consistency: Use consistent hardware for benchmarks

Troubleshooting

Common Issues

"Benchmark results vary widely" - Ensure system is not under load during testing - Close other applications - Run multiple iterations and average results

"Memory usage keeps growing" - Check for memory leaks in test code - Ensure proper cleanup after benchmarks - Monitor garbage collection frequency

"Performance suddenly degraded" - Check recent code changes - Verify no unintended dependencies were added - Profile specific operations that became slower

Getting Help

  • Check existing benchmark results for comparison
  • Run individual benchmark categories to isolate issues
  • Use Node.js profiling tools for detailed analysis
  • Review performance-related GitHub issues

Contributing Performance Tests

When adding new features to expr-eval:

  1. Add Corresponding Benchmarks: New features should include performance tests
  2. Verify Performance Impact: Ensure new features don't regress existing performance
  3. Document Performance Characteristics: Include expected performance ranges
  4. Update Baselines: Establish new performance baselines when architecture changes

For more detailed information about specific benchmarks, see the individual benchmark files in the benchmarks/ directory.