0

Benchmarking — measuring the speed of code under controlled conditions

Intermediate5 min read·eng-20-008
interviewperformance

Concept

Benchmarking — measuring the performance of a specific piece of code or operation under controlled conditions. The goal is to compare approaches or verify that performance meets requirements.

Benchmarking vs profiling:

  • Benchmarking: "Is approach A faster than approach B?" or "Does this function meet our 50ms target?" Isolated measurement.
  • Profiling: "Where is the application spending its time overall?" Whole-system measurement.

What makes a good benchmark:

  • Warm up: Run the code a few times before measuring to warm up JIT, OPcache, CPU caches.
  • Many iterations: Run hundreds or thousands of times, take the average (or median).
  • Controlled environment: Same hardware, no other processes consuming CPU.
  • Measure the right thing: Don't include setup code in the timed section.
  • Statistical significance: Report mean, standard deviation, min/max — not just average.

PHP benchmarking tools:

  • PHPBench: Dedicated benchmarking framework. Run thousands of iterations, report mean time per iteration.
  • microtime(true): Quick manual timing for one-off measurements.
  • Benchmark PHP: php -d opcache.enable_cli=1 -r "..." for micro-benchmarks.

Common pitfalls:

  • Micro-benchmark fallacy: "Function X is 3x faster!" — but in production, the bottleneck is I/O, not that function.
  • Optimizing the wrong thing: Always profile first, then benchmark the slow part.
  • Comparing incomparable things: Benchmark in the same environment, same PHP version, same data size.

Code Example

php
<?php
// PHPBENCH — proper benchmarking framework
// composer require phpbench/phpbench --dev

/**
 * @BeforeMethods({"setUp"})
 * @Iterations(5)
 * @Revs(1000)
 * @Warmup(10)
 */
class StringBench
{
    private array $data;

    public function setUp(): void
    {
        $this->data = range(1, 100);
    }

    /** @Subject */
    public function bench_implode(): void
    {
        implode(',', $this->data);
    }

    /** @Subject */
    public function bench_join(): void
    {
        join(',', $this->data); // alias for implode
    }

    /** @Subject */
    public function bench_sprintf(): void
    {
        $result = '';
        foreach ($this->data as $item) {
            $result .= $item . ',';
        }
        rtrim($result, ',');
    }
}

// Run: ./vendor/bin/phpbench run benchmarks/ --report=aggregate
// Output:
// benchmark           subject          mean      stdev     diff
// StringBench         bench_implode    0.62μs    0.05μs    1.00x
// StringBench         bench_join       0.63μs    0.04μs    1.02x
// StringBench         bench_sprintf    3.84μs    0.12μs    6.19x

// MANUAL TIMING — quick and dirty
function benchmarkApproach(callable $fn, int $iterations = 1000): float
{
    // Warm up
    for ($i = 0; $i < 10; $i++) $fn();

    // Measure
    $start = microtime(true);
    for ($i = 0; $i < $iterations; $i++) $fn();
    $elapsed = (microtime(true) - $start) * 1000;

    return $elapsed / $iterations; // ms per iteration
}

$timeA = benchmarkApproach(fn() => array_map(fn($x) => $x * 2, range(1, 100)));
$timeB = benchmarkApproach(fn() => array_reduce(range(1, 100), fn($c, $x) => [...$c, $x * 2], []));

printf("array_map: %.4fms\n", $timeA);
printf("array_reduce: %.4fms\n", $timeB);
printf("Difference: %.1fx\n", $timeB / $timeA);