强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

PHP 完全指南 / 第 23 章 — 性能优化

第 23 章 — 性能优化:OPcache、JIT、Profiling 与 Blackfire

23.1 OPcache

OPcache 将 PHP 脚本编译为字节码并缓存在共享内存中,避免每次请求重新解析和编译。

; php.ini 配置
[opcache]
opcache.enable=1
opcache.enable_cli=0
opcache.memory_consumption=256       ; MB
opcache.interned_strings_buffer=16   ; MB
opcache.max_accelerated_files=20000
opcache.max_wasted_percentage=10
opcache.validate_timestamps=0        ; 生产环境关闭(需要手动清除缓存)
opcache.revalidate_freq=0
opcache.save_comments=1
opcache.enable_file_override=1

; 预加载(PHP 7.4+)
opcache.preload=/var/www/app/preload.php
<?php
// preload.php — 预加载常用类
opcache_compile_file('/var/www/app/src/Models/User.php');
opcache_compile_file('/var/www/app/src/Services/AuthService.php');

OPcache 性能对比

场景无 OPcache有 OPcache提升
简单 API5ms2ms60%
复杂页面50ms25ms50%
启动时间100ms10ms90%

23.2 JIT 编译器

PHP 8.0 引入的 JIT(Just-In-Time)编译器将热点代码编译为机器码。

[opcache]
opcache.jit=1255          ; 模式配置
opcache.jit_buffer_size=128  ; MB

JIT 配置详解

字段说明
第 1 位1启用
第 2 位2按请求触发
第 3 位5总是在调用时 JIT
第 4 位5使用寄存器分配

JIT 主要受益于 CPU 密集型任务,对 I/O 密集型 Web 请求提升有限。


23.3 Profiling(性能分析)

23.3.1 Xdebug Profiling

[xdebug]
xdebug.mode=profile
xdebug.output_dir=/tmp/xdebug
xdebug.profiler_output_name=cachegrind.out.%p
xdebug.start_with_request=trigger  ; 需要 ?XDEBUG_PROFILE=1 触发

23.3.2 Blackfire.io

# 安装 Blackfire
curl -sS https://packages.blackfire.io/gpg.key | sudo apt-key add -
echo "deb http://packages.blackfire.io/debian any main" | sudo tee /etc/apt/sources.list.d/blackfire.list
sudo apt update && sudo apt install blackfire-php
<?php
// 手动触发 Profiling
blackfire()->enable();
// 要分析的代码
blackfire()->disable();

23.4 优化技巧

23.4.1 数据库优化

<?php
// ❌ N+1 查询问题
$users = $pdo->query('SELECT * FROM users')->fetchAll();
foreach ($users as $user) {
    $orders = $pdo->query("SELECT * FROM orders WHERE user_id = {$user['id']}")->fetchAll();
}

// ✅ 批量查询
$userIds = array_column($users, 'id');
$placeholders = implode(',', array_fill(0, count($userIds), '?'));
$stmt = $pdo->prepare("SELECT * FROM orders WHERE user_id IN ({$placeholders})");
$stmt->execute($userIds);
$allOrders = $stmt->fetchAll();

// 按 user_id 分组
$ordersByUser = [];
foreach ($allOrders as $order) {
    $ordersByUser[$order['user_id']][] = $order;
}

23.4.2 缓存策略

<?php
class CacheManager
{
    private Redis $redis;

    public function remember(string $key, int $ttl, callable $callback): mixed
    {
        $cached = $this->redis->get($key);
        if ($cached !== false) {
            return json_decode($cached, true);
        }

        $value = $callback();
        $this->redis->setex($key, $ttl, json_encode($value));
        return $value;
    }
}

// 使用
$users = $cache->remember('users:active', 3600, function () use ($db) {
    return $db->query('SELECT * FROM users WHERE active = 1')->fetchAll();
});

23.4.3 代码级优化

<?php
// 1. 使用 strict_types(避免隐式转换开销)
declare(strict_types=1);

// 2. 避免在循环中调用函数
// ❌
for ($i = 0; $i < count($array); $i++) {}
// ✅
$length = count($array);
for ($i = 0; $i < $length; $i++) {}

// 3. 使用 isset 代替 array_key_exists
isset($array['key']);  // 更快

// 4. 避免不必要的对象创建
// 5. 使用生成器处理大数据
// 6. 字符串拼接用 implode 代替 .=

// 7. 使用 SplFixedArray 替代普通数组(已知大小时)
$arr = new SplFixedArray(1000);  // 性能更好

23.5 内存优化

<?php
// 监控内存使用
echo memory_get_usage();         // 当前使用量
echo memory_get_peak_usage();    // 峰值使用量
echo memory_get_usage(true);     // 实际分配量

// 释放变量
unset($largeArray);
$largeArray = null;  // 更快释放

// 生成器替代数组
function processLargeFile(string $file): Generator
{
    $handle = fopen($file, 'r');
    while (($line = fgets($handle)) !== false) {
        yield $line;
    }
    fclose($handle);
}

// 流式处理
$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
$stmt = $pdo->query('SELECT * FROM huge_table');
while ($row = $stmt->fetch()) {
    // 逐行处理
}

23.6 业务场景:性能监控

<?php
class PerformanceMonitor
{
    private float $startTime;
    private array $metrics = [];

    public function __construct()
    {
        $this->startTime = microtime(true);
    }

    public function record(string $label, callable $callback): mixed
    {
        $start = microtime(true);
        $startMemory = memory_get_usage();

        $result = $callback();

        $this->metrics[] = [
            'label'       => $label,
            'elapsed_ms'  => round((microtime(true) - $start) * 1000, 2),
            'memory_bytes'=> memory_get_usage() - $startMemory,
        ];

        return $result;
    }

    public function getReport(): array
    {
        return [
            'total_elapsed_ms' => round((microtime(true) - $this->startTime) * 1000, 2),
            'peak_memory'      => memory_get_peak_usage(),
            'metrics'          => $this->metrics,
        ];
    }
}

23.7 扩展阅读


上一章第 22 章 — 安全 下一章第 24 章 — 框架概览