Skip to content

Simdjson 高性能 JSON 解析

Simdjson 是一个利用 CPU SIMD 指令(如 SSE 4.2、AVX2、ARM NEON)进行 JSON 解析的超高性能扩展。它基于 Daniel Lemire 的 simdjson 库,解析速度可达数 GB/s,比标准 json_decode() 快数倍到数十倍。本节讲解 PHP 中 Simdjson 扩展的使用。

前置知识

阅读本节前,建议先了解:JSON 编解码

基础概念

Simdjson 的优势

  • 极高速度:利用 SIMD 指令并行解析,比 json_decode 快 5~50 倍
  • 低内存:解析过程中内存分配更少
  • 惰性解码:按需解码字段,不必解码整个 JSON
  • 标准化 API:提供与标准 JSON 类似但更丰富的接口

安装

bash
# PECL 安装
pecl install simdjson

# 编译安装
git clone https://github.com/simdphp/simdjson.git
cd simdjson
phpize
./configure
make && make install

# php.ini
extension=simdjson.so

检查安装

php
<?php
declare(strict_types=1);

if (!extension_loaded('simdjson')) {
    echo "Simdjson 扩展未安装" . PHP_EOL;
} else {
    echo "Simdjson 已安装" . PHP_EOL;
    // simdjson_is_supported() 检查当前 CPU 是否支持 SIMD 加速
    echo "CPU 支持 SIMD: " . (simdjson_is_supported() ? '是' : '否') . PHP_EOL;
}

基本用法

simdjson_decode

php
<?php
declare(strict_types=1);

use SimdJson\SimdJson;

$json = '{"name":"张三","age":30,"active":true,"scores":[95,88,92]}';

// 基本解码(等同于 json_decode)
$data = simdjson_decode($json, true);
// ['name' => '张三', 'age' => 30, 'active' => true, 'scores' => [95, 88, 92]]

// 解码为对象
$obj = simdjson_decode($json, false);
// stdClass { name: '张三', age: 30, active: true, scores: [95, 88, 92] }

// 深度限制
$data = simdjson_decode($json, true, 512);

simdjson_encode

php
<?php
declare(strict_types=1);

$data = [
    'name'  => '张三',
    'age'   => 30,
    'items' => array_fill(0, 100, ['id' => 1, 'value' => 'test']),
];

// 基本编码
$json = simdjson_encode($data);

// 带选项编码
$json = simdjson_encode($data, SIMDJSON_PRETTY_PRINT | SIMDJSON_UNESCAPED_UNICODE);

错误处理

php
<?php
declare(strict_types=1);

// Simdjson 使用异常进行错误处理
try {
    $data = simdjson_decode('{invalid}', true);
} catch (SimdJsonException $e) {
    echo "Simdjson 错误: " . $e->getMessage() . PHP_EOL;
    echo "错误码: " . $e->getCode() . PHP_EOL;
}

// 错误码与标准 json_last_error() 对应

惰性解码(KeyPath)

概念

Simdjson 的杀手级特性是惰性解码:只解码你需要的字段,而不必解码整个 JSON 文档。对于大 JSON 文档,这可以极大提升性能。

php
<?php
declare(strict_types=1);

$json = '{
    "user": {
        "id": 12345,
        "name": "张三",
        "email": "zhangsan@example.com",
        "profile": {
            "bio": "一段很长的自我介绍...",
            "address": {
                "city": "北京",
                "district": "朝阳区",
                "street": "长安街1号",
                "zip": "100000"
            }
        }
    },
    "metadata": {
        "version": "1.0",
        "timestamp": 1700000000
    }
}';

// 惰性提取单个值 - 不需要解码整个 JSON
$name = simdjson_key_value($json, 'user.name');
echo "用户名: {$name}" . PHP_EOL;

$city = simdjson_key_value($json, 'user.profile.address.city');
echo "城市: {$city}" . PHP_EOL;

// 检查 key 是否存在
$hasEmail = simdjson_key_exists($json, 'user.email');
echo "有邮箱: " . ($hasEmail ? '是' : '否') . PHP_EOL;

$hasPhone = simdjson_key_exists($json, 'user.phone');
echo "有电话: " . ($hasPhone ? '是' : '否') . PHP_EOL;

批量提取

php
<?php
declare(strict_types=1);

// 从 JSON 数组中批量提取字段
$usersJson = '[
    {"id":1,"name":"张三","email":"zhangsan@example.com"},
    {"id":2,"name":"李四","email":"lisi@example.com"},
    {"id":3,"name":"王五","email":"wangwu@example.com"}
]';

// 提取所有 name 字段
$names = simdjson_key_value($usersJson, '*.name');
// ['张三', '李四', '王五']

// 提取所有 id 字段
$ids = simdjson_key_value($usersJson, '*.id');
// [1, 2, 3]

// 带条件的提取
$emails = simdjson_key_value($usersJson, '*.email');
print_r($emails);

详细说明

选项常量

php
<?php
declare(strict_types=1);

// Simdjson 选项常量
// SIMDJSON_PRETTY_PRINT          - 格式化输出(编码时)
// SIMDJSON_UNESCAPED_SLASHES    - 不转义斜杠
// SIMDJSON_UNESCAPED_UNICODE    - 不转义 Unicode

// 解码选项
$json = '{"name":"张三","path":"C:\\Users"}';

$data = simdjson_decode($json, true, 512, SIMDJSON_UNESCAPED_SLASHES);

性能对比

php
<?php
declare(strict_types=1);

// 性能测试脚本
$jsonFile = file_get_contents('large-data.json');

$iterations = 100;

// 标准函数
$start = microtime(true);
for ($i = 0; $i < $iterations; $i++) {
    $data = json_decode($jsonFile, true);
}
$jsonTime = microtime(true) - $start;

// Simdjson
$start = microtime(true);
for ($i = 0; $i < $iterations; $i++) {
    $data = simdjson_decode($jsonFile, true);
}
$simdTime = microtime(true) - $start;

echo "json_decode:    {$jsonTime}s" . PHP_EOL;
echo "simdjson_decode: {$simdTime}s" . PHP_EOL;
echo "加速比: " . round($jsonTime / $simdTime, 2) . "x" . PHP_EOL;

isValid 验证

php
<?php
declare(strict_types=1);

// 使用 Simdjson 验证 JSON
$json = '{"valid": true}';
$isValid = simdjson_is_valid($json, $errorCode, $errorMessage);

if ($isValid) {
    echo "JSON 合法" . PHP_EOL;
} else {
    echo "错误 [{$errorCode}]: {$errorMessage}" . PHP_EOL;
}

// 高速验证,适合大文件
$largeJson = file_get_contents('big-data.json');
if (simdjson_is_valid($largeJson)) {
    $data = simdjson_decode($largeJson, true);
}

实战示例

高性能日志解析

php
<?php
declare(strict_types=1);

/**
 * 使用 Simdjson 解析大型 JSON 日志文件
 */
class JsonLogParser
{
    /**
     * 逐行解析 JSON 日志
     */
    public function parseFile(string $filePath, callable $onEntry): array
    {
        $stats = ['total' => 0, 'parsed' => 0, 'errors' => 0];

        $file = fopen($filePath, 'r');
        if ($file === false) {
            throw new RuntimeException("无法打开文件");
        }

        while (($line = fgets($file)) !== false) {
            $line = trim($line);
            if (empty($line)) {
                continue;
            }

            $stats['total']++;

            try {
                // 使用 Simdjson 高速解码
                $entry = simdjson_decode($line, true);
                $onEntry($entry);
                $stats['parsed']++;
            } catch (SimdJsonException $e) {
                $stats['errors']++;
            }
        }

        fclose($file);
        return $stats;
    }

    /**
     * 从大量日志中提取特定字段(惰性解码)
     */
    public function extractField(string $filePath, string $keyPath): array
    {
        $values = [];

        $file = fopen($filePath, 'r');
        if ($file === false) {
            return $values;
        }

        while (($line = fgets($file)) !== false) {
            $line = trim($line);
            if (empty($line)) {
                continue;
            }

            // 惰性提取,无需解码整行
            $value = simdjson_key_value($line, $keyPath);
            if ($value !== null) {
                $values[] = $value;
            }
        }

        fclose($file);
        return $values;
    }
}

// 使用示例
// $parser = new JsonLogParser();
// $stats = $parser->parseFile('app.json.log', function (array $entry): void {
//     echo "[{$entry['level']}] {$entry['message']}" . PHP_EOL;
// });
//
// $levels = $parser->extractField('app.json.log', 'level');

API 响应性能优化

php
<?php
declare(strict_types=1);

/**
 * 高性能 JSON API 处理器
 */
class FastApiHandler
{
    /**
     * 处理 JSON 请求
     */
    public function handleRequest(string $body): array
    {
        try {
            // 使用 Simdjson 高速解码
            $data = simdjson_decode($body, true);
            return $this->processData($data);
        } catch (SimdJsonException $e) {
            return ['error' => '无效的 JSON 格式'];
        }
    }

    /**
     * 构建响应(使用 Simdjson 编码)
     */
    public function buildResponse(array $data): string
    {
        return simdjson_encode([
            'code'    => 0,
            'data'    => $data,
            'time'    => microtime(true),
        ], SIMDJSON_UNESCAPED_UNICODE);
    }

    private function processData(array $data): array
    {
        // 业务逻辑处理
        return $data;
    }
}

注意事项

兼容性

php
<?php
declare(strict_types=1);

// 检测 CPU 是否支持 SIMD
if (!simdjson_is_supported()) {
    // 回退到标准 json_decode
    $data = json_decode($json, true);
} else {
    $data = simdjson_decode($json, true);
}

功能限制

注意

  • Simdjson 不支持 json_encode() 的所有选项(如 JSON_FORCE_OBJECT
  • 惰性解码的 key path 语法有限制(不支持通配符嵌套等复杂模式)
  • 某些边界情况可能与标准 json_decode() 行为不同

何时使用 Simdjson

场景推荐使用
大型 JSON 文件解析Simdjson
大量 JSON 请求处理Simdjson
只需提取部分字段Simdjson (key path)
小型 JSONjson_decode 足够
需要 JSON_FORCE_OBJECTjson_encode
需要 JSON_PRESERVE_ZERO_FRACTIONjson_encode

最佳实践

  1. 大数据用 Simdjson:超过 1MB 的 JSON 建议使用 Simdjson
  2. 惰性提取:只需部分字段时使用 key path
  3. 回退机制:检测 simdjson_is_supported() 提供回退方案
  4. 性能测试:根据实际数据测试两种方案的性能差异
  5. 一致性行为:注意 Simdjson 与标准函数的细微差异

下一节

继续学习:IMAP / POP3 / SMTP

参考链接