0

OPcache configuration for production — key settings

Intermediate5 min read·php-15-001
performanceinterview

Concept

Benchmarking is the practice of measuring code execution time and memory usage to identify performance bottlenecks. In PHP, this starts with microbenchmarking tools and escalates to profilers for production-representative analysis.

microtime(true): Returns the current time as a float (seconds.microseconds). The simplest benchmarking technique: record time before and after a block of code. Subtract to get elapsed seconds. Multiply by 1000 for milliseconds.

memory_get_usage(bool $real_usage = false): Returns current memory used by PHP. With $real_usage = true, returns memory allocated from the system (rounded up to page size). With false, returns memory actually used by the emalloc allocator.

memory_get_peak_usage(bool $real_usage = false): Returns peak memory used since the script started. Essential for measuring memory overhead of data structures.

Pitfalls:

  • Single runs are noisy. Average over many iterations (1000+) for short operations.
  • JIT warming: the first few iterations may be slower. Discard or warm up.
  • GC interference: the garbage collector running during a benchmark skews results. Consider gc_disable() / gc_enable() around critical benchmarks.
  • OPcache: if not enabled in CLI tests, you're measuring unoptimized bytecode. Enable OPcache in php.ini for realistic CLI benchmarks.
  • Micro-optimizations rarely matter. Profile first to find actual bottlenecks.

Code Example

php
<?php
declare(strict_types=1);

// Basic microbenchmark
function bench(callable $fn, int $iterations = 10_000): array
{
    // Warmup
    for ($i = 0; $i < 100; $i++) $fn();

    gc_disable();
    $memBefore = memory_get_usage(true);
    $start = microtime(true);

    for ($i = 0; $i < $iterations; $i++) {
        $fn();
    }

    $elapsed = microtime(true) - $start;
    $memAfter = memory_get_usage(true);
    gc_enable();

    return [
        'iterations' => $iterations,
        'total_ms'   => round($elapsed * 1000, 3),
        'avg_us'     => round($elapsed / $iterations * 1_000_000, 3),
        'mem_kb'     => round(($memAfter - $memBefore) / 1024, 1),
    ];
}

// Compare two implementations
$a = bench(fn() => implode('', array_map(fn($i) => "item_$i", range(1, 100))));
$b = bench(fn() => (function() {
    $buf = '';
    for ($i = 1; $i <= 100; $i++) $buf .= "item_$i";
    return $buf;
})());

echo "implode+array_map: {$a['avg_us']}μs/op\n";
echo "string concat:     {$b['avg_us']}μs/op\n";

// Memory benchmark
$before = memory_get_peak_usage(true);
$data = range(1, 100_000); // array of 100k integers
$peak = memory_get_peak_usage(true);
echo "Array of 100k ints: " . round(($peak - $before) / 1024 / 1024, 1) . " MB\n";

// Alternatively use a generator
function gen100k() { for ($i = 1; $i <= 100_000; $i++) yield $i; }
$genPeak = memory_get_peak_usage(true);
foreach (gen100k() as $v) {} // process without storing all in memory
echo "Generator: " . round((memory_get_peak_usage(true) - $genPeak) / 1024, 1) . " KB\n";
// ~3.5MB array vs ~0.5KB generator