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) |
| 小型 JSON | json_decode 足够 |
| 需要 JSON_FORCE_OBJECT | json_encode |
| 需要 JSON_PRESERVE_ZERO_FRACTION | json_encode |
最佳实践
- 大数据用 Simdjson:超过 1MB 的 JSON 建议使用 Simdjson
- 惰性提取:只需部分字段时使用 key path
- 回退机制:检测
simdjson_is_supported()提供回退方案 - 性能测试:根据实际数据测试两种方案的性能差异
- 一致性行为:注意 Simdjson 与标准函数的细微差异
下一节
继续学习:IMAP / POP3 / SMTP