All Skills

Use when optimizing PHP performance, configuring OPcache or JIT, setting up caching (Redis, Memcached), tuning database queries (PDO), choosing faster function alternatives, managing memory, or profiling. Covers OPcache configuration, opcache.validate_timestamps, preload, preloading, php.ini tuning, memory_limit, realpath_cache, JIT tracing, micro-optimizations, function choices, loops, casting, memory management, connection pooling, lazy loading, N+1 query problem, SPL data structures, SplFixedArray, PDO best practices, caching patterns, Xdebug profiler, and Blackfire production profiling.

R
$npx skills add peixotorms/odinlayer-skills --skill php-performance

PHP Performance

OPcache

Production Configuration

; /etc/php/8.x/fpm/conf.d/10-opcache.ini
opcache.enable=1
opcache.memory_consumption=256          ; MB — increase for large codebases
opcache.interned_strings_buffer=16      ; MB — reduces string duplication
opcache.max_accelerated_files=20000     ; rounded to next prime internally
opcache.validate_timestamps=0           ; DISABLE in production — requires manual reset on deploy
opcache.save_comments=1                 ; required by Doctrine, PHPUnit, many frameworks
opcache.optimization_level=0x7FFEBFFF   ; all safe optimizations (default)

Development Configuration

opcache.enable=1
opcache.validate_timestamps=1           ; auto-detect file changes
opcache.revalidate_freq=0               ; check on every request

Key Directives

DirectiveDefaultProductionNotes
opcache.enable11Always on
opcache.enable_cli00 (or 1 for workers)Enable for long-running CLI
opcache.memory_consumption128256-512MB of shared memory
opcache.interned_strings_buffer816-32MB for deduplicated strings
opcache.max_accelerated_files1000020000-50000Max cached scripts
opcache.validate_timestamps10Disable in prod — reset on deploy
opcache.revalidate_freq2N/A (timestamps off)Seconds between checks
opcache.file_update_protection22Skip files modified <N seconds ago
opcache.max_wasted_percentage55-10Trigger restart at this waste %

Preloading (PHP 7.4+)

opcache.preload=/var/www/app/preload.php
opcache.preload_user=www-data
// preload.php — loaded once at server startup
$directory = new RecursiveDirectoryIterator(__DIR__ . '/src');
$files = new RecursiveIteratorIterator($directory);
$php = new RegexIterator($files, '/\.php$/');
foreach ($php as $file) {
    opcache_compile_file($file->getRealPath());  // compiles without executing
}
RuleDetail
opcache_compile_file()Compiles without executing — safe for any order
require_once alternativeExecutes code — order matters (base classes first)
Constants NOT preloadedOnly functions, classes, traits, interfaces
Requires server restartCan't update preloaded code at runtime
FPM/mod_php onlyNot useful for one-off CLI scripts
Check opcache.preload_userMust have read access to files

JIT (PHP 8.0+)

opcache.jit=tracing             ; recommended mode (or "function" for simpler JIT)
opcache.jit_buffer_size=128M    ; 0 = JIT disabled
ModeValueBest for
disablePermanent offCannot re-enable at runtime
offToggleable offCan enable later via ini_set
tracing1254Recommended — traces hot loops/paths
function1205Simpler — JIT per function, less overhead
RuleDetail
CPU-bound workloads benefitMath, data processing, tight loops
I/O-bound code: minimal gainDB queries, HTTP calls dominate runtime
opcache.jit_hot_loop = 64Iterations before loop gets JIT-compiled
opcache.jit_hot_func = 127Calls before function gets JIT-compiled
Monitor with opcache_get_status()Check jit.buffer_free for buffer usage

Monitoring & Management

// Check cache health
$status = opcache_get_status();
$hitRate = $status['opcache_statistics']['opcache_hit_rate'];  // target: >98%
$wasted = $status['memory_usage']['current_wasted_percentage'];
$full = $status['cache_full'];

// Reset cache (deploy hook)
opcache_reset();  // clears entire in-memory cache

// Invalidate single file
opcache_invalidate('/path/to/file.php', true);  // force=true ignores mtime
IndicatorHealthyAction if unhealthy
Hit rate>98%Increase max_accelerated_files or memory_consumption
Wasted memory<5%Restart PHP-FPM or increase max_wasted_percentage
cache_fullfalseIncrease memory_consumption
oom_restarts0Increase memory — OOM caused forced restarts

File Cache (Shared Hosting / CLI)

opcache.file_cache=/var/cache/opcache       ; persistent across restarts
opcache.file_cache_only=0                   ; 1 = skip shared memory entirely
opcache.file_cache_consistency_checks=1     ; validate checksums

Database (PDO)

// Recommended PDO configuration
$pdo = new PDO('mysql:host=localhost;dbname=app;charset=utf8mb4', $user, $pass, [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,   // throw exceptions
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,         // assoc arrays
    PDO::ATTR_EMULATE_PREPARES   => false,                    // real prepared statements
]);

// Named parameters — clearer for multiple params
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email AND active = :active');
$stmt->execute(['email' => $email, 'active' => 1]);
$user = $stmt->fetch();

// Transactions
$pdo->beginTransaction();
try {
    $pdo->prepare('UPDATE accounts SET balance = balance - ? WHERE id = ?')->execute([$amount, $from]);
    $pdo->prepare('UPDATE accounts SET balance = balance + ? WHERE id = ?')->execute([$amount, $to]);
    $pdo->commit();
} catch (\Throwable $e) {
    $pdo->rollBack();
    throw $e;
}
RuleDetail
ERRMODE_EXCEPTIONAlways — silent failures hide bugs
EMULATE_PREPARES = falseReal server-side prepared statements — actual SQL injection protection
FETCH_ASSOC defaultLess memory than FETCH_BOTH (default)
charset=utf8mb4 in DSNMySQL: full Unicode including emoji
Named params :nameClearer than positional ? for 3+ parameters
Transactions for multi-statementAtomicity — all succeed or all roll back
fetchAll() cautionLoads entire result into memory — use fetch() in loop for large results

Caching (Redis / Memcached)

// Redis — cache-aside pattern
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

$key = 'user:' . $userId;
$cached = $redis->get($key);
if ($cached !== false) {
    return json_decode($cached, true);
}
$data = $db->fetchUser($userId);
$redis->setex($key, 3600, json_encode($data));  // TTL 1 hour
return $data;

// Memcached
$mc = new Memcached();
$mc->addServer('127.0.0.1', 11211);
$mc->set('key', $value, 3600);
$value = $mc->get('key');
LayerToolWhen
BytecodeOPcacheAlways — eliminates recompilation
Application dataRedis / MemcachedDB query results, API responses, computed values
HTTPReverse proxy (Varnish, Nginx)Full page / fragment caching
Preloading (7.4+)opcache.preloadFramework classes loaded at startup
PatternDetail
Cache-asideCheck cache → miss → fetch → store → return
Invalidate on writeClear/update cache when data changes
TTL expirationSet appropriate time-to-live per data type
Cache stampedeUse locking or probabilistic early refresh
fetchAll() + cacheLoad once, cache result, avoid repeated queries

SPL Data Structures

ClassUse forvs Array
SplFixedArrayLarge fixed-size numeric arrays~50% less memory
SplStackLIFO stack (push/pop)Enforces stack semantics
SplQueueFIFO queue (enqueue/dequeue)Enforces queue semantics
SplPriorityQueuePriority-based processingBuilt-in priority ordering
SplHeap / SplMinHeap / SplMaxHeapHeap operationsO(log n) insert/extract
SplDoublyLinkedListEfficient insert/remove at endsBetter for frequent head/tail ops
SplObjectStorageMap objects to dataObject identity as key

Micro-Optimizations

Function Choices — Prefer Faster Alternatives

SlowerFasterWhy
array_key_exists($k, $a)isset($a[$k])Language construct, no function call (but returns false for null values)
in_array($v, $arr) (repeated)isset($flipped[$v])O(1) hash lookup vs O(n) linear scan — flip once with array_flip()
count($arr) in loop condition$len = count($arr) before loopAvoid recalculating each iteration
strpos($h, $n) !== falsestr_contains($h, $n)Cleaner, same speed (PHP 8.0+)
substr($s, 0, 3) === 'foo'str_starts_with($s, 'foo')Purpose-built, clearer (PHP 8.0+)
intval($v) / settype()(int) $vDirect cast — no function call overhead
floatval($v)(float) $vDirect cast
strval($v)(string) $vDirect cast
sprintf() for simple join. concatenation or implode()sprintf() parses format string
array_push($a, $v)$a[] = $vLanguage construct, no function call
== comparison=== comparisonNo type juggling overhead
array_merge() in loop$result[] = $items + array_merge(...$result)Single merge at end

Strings & Arrays

PatternDetail
String building in loopsCollect in array, implode() at end — not .=
array_column($rows, 'id')Extract column from 2D array — faster than foreach
array_map() for transformsClean for simple callbacks; foreach for complex logic
array_filter() preserves keysUse array_values() to reindex if needed
Generators for large datayield instead of building arrays in memory
SplFixedArrayPre-sized array for large fixed-size datasets
in_array() strict modeAlways in_array($v, $a, true) — avoids type juggling

Memory Management

// Monitor memory usage
echo memory_get_usage(true);      // real OS allocation
echo memory_get_peak_usage(true); // max during script

// Free large variables explicitly
unset($largeArray);

// Force garbage collection in long-running processes
gc_collect_cycles();
RuleDetail
unset() large variablesFrees memory when no other references exist
gc_collect_cycles()Manual GC for long-running CLI scripts
Generators over arraysProcess items one-by-one instead of loading all into memory
Streaming file readsfgets() / SplFileObject — never file_get_contents() on large files
Typed propertiesEnable engine optimizations and reduce memory
memory_get_peak_usage(true)Track high-water mark during profiling

Loops

// Cache count outside loop
$len = count($items);
for ($i = 0; $i < $len; $i++) { }

// foreach is idiomatic for arrays — use it
foreach ($items as $key => $value) { }

// Unset reference after foreach by-reference
foreach ($items as &$item) { $item = transform($item); }
unset($item);  // CRITICAL — prevents bugs from lingering reference

// array_map for simple transforms
$names = array_map(fn($u) => $u->name, $users);
PatternBest for
foreachDefault for iterating arrays — idiomatic, fast
for with cached countIndex-based access, early break by position
array_map()Simple 1:1 transforms with clean callback
array_filter()Filtering with predicate
array_walk()Modify in-place by reference (less common)
while + fgets()Line-by-line file processing
Generator + foreachLazy iteration over large datasets

Profiling

ToolUse for
opcache_get_status()OPcache hit rates, memory, JIT stats
memory_get_peak_usage()Memory high-water mark
Xdebug profilerFunction-level timing and call graphs
BlackfireProduction-safe profiling with recommendations
TidewaysAPM with low overhead
RuleAlways profile before optimizing — don't guess