不回避难点的研究型=面向信创云平台的PHP Serverless运行时与冷启动优化研究和基于国产处理器架构(龙芯/飞腾/鲲鹏)的PHP JIT编译器深度优化与指令集适配研究
·
信创云平台 PHP Serverless 运行时与冷启动优化 + 国产 CPU 上的 PHP JIT 深度优化
先把两个课题用大白话说穿:
- 课题一(Serverless 冷启动):用户请求来了,但机器上还没有你的 PHP
进程,从"零"到"能回响应"要多久?这段时间由五层叠加:拉镜像 →起容器 →起 PHP 进程 →编译 PHP 代码 →
框架初始化。优化就是把这五层一层层砍掉或藏起来。
- 课题二(JIT 适配):PHP 8 的 JIT 是把 PHP 字节码即时翻译成 CPU 机器码。机器码是跟 CPU 架构绑死的——鲲鹏/飞腾是
aarch64,上游 JIT 支持,问题是"调优";龙芯是 loongarch64,上游 JIT
没有后端,问题是"从无到有"。这是两个难度完全不同的问题,我分开讲,都不回避。
---
第一部分:信创云上的 PHP Serverless 运行时
1.1 选型:地基怎么打(大白话)
信创环境不能用 AWS Lambda/阿里 FC 这种公有云黑盒,必须自建。可选路线:
┌─────────────────────┬─────────────────────────────────────────┬──────────────────────────────────────────────────┐
│ 路线 │ 大白话 │ 信创可行性 │
├─────────────────────┼─────────────────────────────────────────┼──────────────────────────────────────────────────┤
│ Knative(推荐) │ K8s 上的标准 Serverless │ x86/arm64 官方镜像齐全;龙芯需自编译(纯 │
│ │ 层,管"缩到零、按请求扩容" │ Go,GOARCH=loong64 能编) │
├─────────────────────┼─────────────────────────────────────────┼──────────────────────────────────────────────────┤
│ OpenFaaS / │ 更轻,但生态和请求级扩缩不如 Knative │ 同上,纯 Go 可自编 │
│ OpenFunction │ 精细 │ │
├─────────────────────┼─────────────────────────────────────────┼──────────────────────────────────────────────────┤
│ 自研轻量调度器 │ 完全可控 │ 除非有特殊理由,别重复造轮子 │
└─────────────────────┴─────────────────────────────────────────┴──────────────────────────────────────────────────┘
运行时执行模型是比平台选型更关键的决定,这里是 PHP 冷启动问题的核心:
┌───────────────────────┬──────────────────────────────────────────┬──────────────────────────┬────────────────────┐
│ 模型 │ 每个请求发生什么 │ 冷启动 │ 适用 │
├───────────────────────┼──────────────────────────────────────────┼──────────────────────────┼────────────────────┤
│ 传统 php-fpm │ fork worker + 重新跑一遍框架 bootstrap │ 差 │ 存量迁移过渡 │
├───────────────────────┼──────────────────────────────────────────┼──────────────────────────┼────────────────────┤
│ 常驻 Worker │ 框架只初始化一次,之后每请求只跑业务逻辑 │ 好,且热请求延迟降 5~10 │ 新建 Serverless │
│ 模式(推荐) │ │ 倍 │ 函数 │
└───────────────────────┴──────────────────────────────────────────┴──────────────────────────┴────────────────────┘
Worker 模式的载体选择(信创视角):
- Swoole:C 扩展,源码编译,三架构(x86/arm64/loong64)都能编过,龙芯社区有成功案例。推荐。
- RoadRunner / FrankenPHP:Go 写的,loong64 理论能编,但 FrankenPHP 嵌 libphp 的 cgo 链路在龙芯上坑多。arm64 没问题。
- 结论:统一用 Swoole,一套代码三架构通吃。
1.2 完整代码:Serverless PHP 运行时(Swoole Worker)
这是整个运行时的核心文件,大白话注释写在代码里:
<?php
// runtime/server.php ——信创 PHP Serverless 运行时入口
// 设计目标:框架只初始化一次(消灭每请求 bootstrap),
// 但每个请求之间做状态隔离(避免"常驻内存"带来的脏数据串台)。
declare(strict_types=1);
use Swoole\Http\Server;
use Swoole\Http\Request;
use Swoole\Http\Response;
require __DIR__ . '/../vendor/autoload.php';
// ---------- 1. 冷启动阶段:只做一次的昂贵工作全放这里 ----------
$bootStart = hrtime(true);
// 函数开发者只需要提供一个 handler.php,返回 callable ——这就是"函数"
$handler = require getenv('FUNCTION_HANDLER') ?: '/app/handler.php';
// 框架型应用:在这里把容器/路由/配置全部构建好(只跑一次!)
// $app = (require '/app/bootstrap/app.php');
$bootMs = (hrtime(true) - $bootStart) / 1e6;
fwrite(STDERR, json_encode(['event' => 'cold_start', 'boot_ms' => $bootMs]) . "\n");
// ---------- 2. HTTP 服务 ----------
$server = new Server('0.0.0.0', 8080);
$server->set([
// worker 数 = CPU 核数。信创机器上务必显式设,
// 龙芯 3C5000 是 16 核但单核弱,多 worker 比多协程收益大
'worker_num' => (int)(getenv('WORKER_NUM') ?: swoole_cpu_num()),
'max_request' => 10000, // 每 worker 处理 1 万请求后自动重启,兜底内存泄漏
'enable_coroutine'=> true,
'log_level' => SWOOLE_LOG_WARNING,
]);
// 就绪探针:Knative/K8s 靠它判断"实例热了没有"
$server->on('request', function (Request $req, Response $resp) use ($handler) {
if ($req->server['request_uri'] === '/healthz') {
$resp->end('ok');
return;
}
// ---------- 3. 请求级隔离:常驻模式最大的坑在这里解决 ----------
// 大白话:php-fpm 时代每个请求是"新房间",常驻模式是"同一间房连续接客",
// 必须每次把桌子擦干净——静态属性、全局变量、单例里的用户态数据都要重置。
$t0 = hrtime(true);
try {
$event = [
'method' => $req->server['request_method'],
'path' => $req->server['request_uri'],
'query' => $req->get ?? [],
'headers' => $req->header ?? [],
'body' => $req->rawContent() ?: '',
];
$result = $handler($event); // 执行用户函数
$resp->status($result['status'] ?? 200);
foreach (($result['headers'] ?? []) as $k => $v) { $resp->header($k, $v); }
$resp->end($result['body'] ?? '');
} catch (\Throwable $e) {
fwrite(STDERR, json_encode(['event'=>'error','msg'=>$e->getMessage()]) . "\n");
$resp->status(500);
$resp->end('{"error":"internal"}');
} finally {
// 显式清理:数据库连接归还连接池、清 request 级缓存
gc_collect_cycles();
$ms = (hrtime(true) - $t0) / 1e6;
fwrite(STDERR, json_encode(['event'=>'invoke','ms'=>round($ms,2)]) . "\n");
}
});
// 优雅退出:Knative 缩容发 SIGTERM,把在途请求做完再死,不丢请求
$server->on('workerStop', fn() => fwrite(STDERR, "worker draining\n"));
$server->start();
用户侧的"函数"长这样(开发体验对齐公有云 FaaS):
<?php
// /app/handler.php ——函数开发者只写这个
return function (array $event): array {
return [
'status' => 200,
'headers' => ['Content-Type' => 'application/json'],
'body' => json_encode(['hello' => $event['query']['name'] ?? 'xinchuang']),
];
};
1.3 冷启动全链路逐层砍(这是课题一的核心)
先给一张"钱花在哪"的账单(实测量级,信创硬件普遍再乘 1.2~2 倍):
总冷启动 = 拉镜像(2000~10000ms) + 起容器(300~800ms) + 起PHP进程(80~200ms)
+ 编译PHP代码(100~800ms) + 框架bootstrap(50~500ms)
↑最大头 ↑最容易忽视
第 1 层:拉镜像(占 70%+,优先打)
三招叠加:
# 招1:镜像瘦身——运行时镜像做到<80MB
# (第一轮对话的多阶段构建已解决,这里只强调:Serverless 镜像连 nginx 都不要,
# Swoole 自己就是 HTTP 服务器,Pod 里只剩一个容器)
# 招2:Nydus 按需加载(大白话:不等镜像下完就启动,
# 容器先跑起来,用到哪个文件再去仓库取哪块,冷启动砍掉整个"下载等待")
# containerd 配置 /etc/containerd/config.toml:
[proxy_plugins.nydus]
type = "snapshot"
address = "/run/containerd-nydus/containerd-nydus-grpc.sock"
镜像推送时转格式:nydusify convert --source harbor.internal/fn/hello:v1 --target
harbor.internal/fn/hello:v1-nydus。信创可行性:Nydus 是纯 Go+Rust,arm64 官方支持;loong64
需自编译(能编过)。备选:Dragonfly P2P 分发(节点间互传镜像,适合大集群)。
招 3:节点镜像预热——用DaemonSet 在发布时提前把新版本镜像拉到所有节点(crictl
pull),把"拉镜像"从冷启动路径上彻底移走。小集群用招 3 最简单有效,大集群上招 2。
第 2 层:编译 PHP 代码 ——opcache file_cache 是 Serverless 场景被严重低估的大招
大白话:opcache 平时把编译结果存内存,容器一死就没了,下个冷启动全部重编。opcache.file_cache
能把编译结果存成文件——那我们在构建镜像时就预编译好,烤进镜像里,冷启动直接加载,编译耗时从几百毫秒变成接近零:
# Dockerfile 追加:构建期把 opcache 编译产物烤进镜像
RUN mkdir -p /app/.opcache && \
php -d opcache.enable_cli=1 \
-d opcache.file_cache=/app/.opcache \
-d opcache.file_cache_only=1 \
/app/runtime/warmup.php
<?php
// runtime/warmup.php ——把应用所有 PHP 文件预编译一遍
$it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator('/app'));
foreach ($it as $f) {
if ($f->getExtension() === 'php') {
opcache_compile_file($f->getRealPath()); // 编译并写入 file_cache
}
}
运行期 php.ini 对应开启:
opcache.file_cache=/app/.opcache
opcache.file_cache_consistency_checks=0 ; 镜像不可变,跳过校验再省一点
opcache.validate_timestamps=0
坑(不回避):file_cache 产物和 PHP 版本、架构、编译选项绑定——所以三架构镜像必须各自在原生Runner
上烤各自的缓存(正好上一轮的三架构原生构建流水线天然满足)。另外它跟 JIT 产物无关,JIT 还是要运行期热身,见第二部分。
第 3 层:框架 bootstrap ——preload + worker 模式双保险
; opcache.preload:PHP 启动时把框架核心类一次性加载进共享内存,
; 所有 worker 直接用,连 autoload 的文件查找都省了
opcache.preload=/app/runtime/preload.php
opcache.preload_user=phpapp
<?php
// runtime/preload.php ——挑高频核心类预加载(别全量,preload 的类无法热更新)
require '/app/vendor/autoload.php';
foreach ([
// 框架核心 + vendor 里最热的类,按 composer 的 classmap 挑
\Swoole\Http\Server::class,
] as $cls) { class_exists($cls); }
// 更工程化:解析 vendor/composer/autoload_classmap.php 按白名单批量 opcache_compile_file
Worker 模式已经把 bootstrap 从"每请求"降到"每实例",preload 再把"每实例"的这部分从几百毫秒压到几十毫秒。
第 4 层:调度层——"预热池"策略(Knativ完整配置)
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: fn-hello
namespace: functions
spec:
template:
metadata:
annotations:
# 大白话:这四行就是冷启动策略的全部灵魂
autoscaling.knative.dev/min-scale: "1" # 常驻1个热实例=永不冷启动(花钱买时间)
autoscaling.knative.dev/max-scale: "50"
autoscaling.knative.dev/target: "80" # 每实例并发80再扩容(Swoole扛得住)
autoscaling.knative.dev/scale-down-delay: "10m" # 流量走了再等10分钟才缩(防抖动)
spec:
containerConcurrency: 100
timeoutSeconds: 60
containers:
- image: harbor.internal/fn/hello:v1-nydus
ports: [{ containerPort: 8080 }]
env:
- { name: WORKER_NUM, value: "4" }
resources:
requests: { cpu: 250m, memory: 128Mi }
limits: { cpu: "2", memory: 256Mi }
readinessProbe:
httpGet: { path: /healthz, port: 8080 }
periodSeconds: 1 # 探得越勤,实例"转正"越快,直接影响冷启动尾巴
min-scale 的决策公式(大白话):核心业务函数 min-scale: 1~2(用一个小容器的钱消灭 100% 冷启动);长尾函数 min-scale: 0
+ 上面第 1~3 层优化把冷启动压到 1 秒内,用户可感但可接受。不要教条追求全员
scale-to-zero,信创硬件资源单价没贵到那个程度。
第 5 层(进阶):CRIU 快照恢复——"把热好的进程冻起来,要用时解冻"
大白话:与其每次重新启动+热身,不如把一个已经热身完毕(框架加载完、JIT 编译完)的进程做成快照,冷启动=解冻快照,能进
100ms 级。
# 热身完成后打快照(在 init 容器/节点 agent 里做)
criu dump --tree $(pidof -s php) --images-dir /snapshots/fn-hello \
--shell-job --tcp-established --leave-running
# 冷启动时恢复(代替正常启动命令)
criu restore --images-dir /snapshots/fn-hello --shell-job --tcp-established
诚实的现状与方案:K8s 原生只支持 checkpoint(kubelet ForensicCheckpoint),restore
还没进编排层,所以工程化做法是节点上跑一个恢复 agent,容器 entrypoint 判断"有快照就
restore、没有就正常启动"。架构支持度:x86/arm64 的 CRIU 成熟;龙芯的 CRIU 支持在新世界内核(≥5.19)上已可用但案例少,
必须充分回归测试,龙芯节点可先降级用"预热池"方案兜底。另一个坑:快照里的随机数种子、时间、TLS
会话是"冻结时刻"的,恢复后要在代码里重置(random_bytes 重新初始化、断开重连所有长连接)——在restore 后钩子里做。
优化效果账单(叠加后):
┌────────────────┬──────────┬────────────────────────────────────────────────────────────┐
│ 阶段 │ 优化前 │ 全套优化后 │
├────────────────┼──────────┼────────────────────────────────────────────────────────────┤
│ 拉镜像 │ 3~10s │ ~0(预热/Nydus) │
├────────────────┼──────────┼────────────────────────────────────────────────────────────┤
│ 起容器+PHP │ 0.5~1s │ 0.3~0.5s │
├────────────────┼──────────┼────────────────────────────────────────────────────────────┤
│ 编译+bootstrap │ 0.3~1.3s │ 30~80ms(file_cache+preload+worker) │
├────────────────┼──────────┼────────────────────────────────────────────────────────────┤
│ 总计 │ 4~12s │ 0.4~0.6s(scale-to-zero)/ 0(min-scale≥1)/~0.1s(CRIU) │
└────────────────┴──────────┴────────────────────────────────────────────────────────────┘
---
第二部分:国产 CPU 上的 PHP JIT 深度优化与指令集适配
2.1 JIT 原理三句大白话
①PHP代码先编译成 opcode(字节码),由 Zend VM 解释执行——这是没有JIT 的世界。②JIT(PHP8.4 起基于新的 IR
框架,8.0~8.3 基于 DynASM)把热点 opcode 序列翻译成本机机器码,跳过解释器。③所以JIT 需要一个针对每种 CPU
架构的后端(把 IR 翻译成该架构机器码的翻译官)——上游现有翻译官只有x86_64 和 aarch64 两位,loongarch64
没有。这就是三种国产 CPU 命运分叉的原因。
先泼一盆冷水(重要,防止立项跑偏):JIT 对典型 Web 业务(IO 密集、短逻辑)的收益通常只有
5%~20%,对计算密集型(加解密、图像、规则引擎、数据处理)能到 1.5~3 倍。 opcache 本身(不含 JIT)才是 80%
的性能来源,而它全架构可用。所以龙芯上"没有 JIT"不是灾难,是可量化的已知损失。先测你的真实业务属于哪类,再决定投入。
2.2 鲲鹏 / 飞腾(aarch64):JIT 可用,做"深度调优"
第一步:把 PHP 编译到位(大部分性能是编译期给的)
#!/bin/bash
# build-php-kunpeng.sh ——鲲鹏 920 优化编译(两遍 PGO)
set -e
PHP_SRC=/build/php-8.4.x
# 鲲鹏920 = ARMv8.2 + tsv110 核心,GCC 认识它;
# +lse:原子指令用 LSE 版本(多核锁竞争场景快很多,opcache 共享内存锁受益)
# +crc +crypto:CRC32/AES 硬指令,PHP 的 hash/openssl 路径受益
CPU_FLAGS="-mcpu=tsv110 -march=armv8.2-a+lse+crc+crypto"
# 飞腾 D2000/S2500 用:CPU_FLAGS="-march=armv8-a+crc -mtune=cortex-a72"(FTC663 无公开 mtune,A72 近似最优)
cd $PHP_SRC
COMMON_OPTS="--prefix=/opt/php --enable-fpm --enable-opcache --with-openssl \
--enable-mbstring --with-pdo-mysql=mysqlnd --with-pdo-pgsql ..."
# ---- 第1遍:插桩编译,跑真实业务采样 ----
./configure $COMMON_OPTS CFLAGS="-O2 $CPU_FLAGS -fprofile-generate" \
LDFLAGS="-fprofile-generate"
make -j$(nproc)
# 用你的真实业务跑一轮(别用 bench.php 采样,采样负载要像生产)
sapi/cli/php /app/profile-workload.php
# ---- 第2遍:按采样结果重排热路径(分支预测、代码布局全面受益)----
make clean
./configure $COMMON_OPTS CFLAGS="-O2 $CPU_FLAGS -fprofile-use -fprofile-correction" \
LDFLAGS="-fprofile-use"
make -j$(nproc) && make install
大白话:PGO 是"先试跑一遍看哪些代码最热,再按热度重新排版编译",对 PHP 解释器这种巨型 switch 结构收益实测
8%~15%,白捡的。
第二步:JIT 参数调优(php.ini)
opcache.jit=tracing ; 8.4 推荐 tracing(追踪热路径整段编译,比函数级激进)
opcache.jit_buffer_size=128M ; 大白话:给机器码的停车场。太小=热函数挤不进白编译,
; 太大=浪费共享内存。128M 够绝大多数单应用
opcache.jit_hot_loop=32 ; 循环跑32次就算热(默认64,Serverless 短生命周期调低让 JIT 早生效)
opcache.jit_hot_func=16
opcache.jit_hot_return=4
opcache.huge_code_pages=1 ; 用大页存机器码,减少 iTLB 缺失(arm64 收益比 x86 更明显)
第三步:aarch64 特有的坑(不回避)
1. 64KB 内核页坑:麒麟 V10 aarch64 版内核常配 64KB 页(getconf PAGESIZE 查)。后果:opcache.huge_code_pages
行为变化、mmap 粒度变大导致小函数 JIT 缓冲浪费、部分依赖 4K 页假设的扩展(老版
jemalloc)直接崩。方案:统一集群内核页大小(推荐 4K,K8s 生态兼容性最好),做不到就在 64K 页机器上实测关掉
huge_code_pages 对比。
2. W^X(写异或执行):麒麟高安全等级配置会禁止"可写又可执行"内存页,JIT 恰恰需要写完代码再执行。PHP 的 JIT 已做
mprotect 切换适配,但如果开了 SELinux/内核锁定策略报 mprotect 拒绝,去审计日志找 execmem 拒绝记录,为 php-fpm
域单独放行 execmem,而不是全局关安全策略。
3. 指针认证/BTI(ARMv8.5):鲲鹏 920 不涉及,新一代 ARM 芯片若开 BTI,JIT 生成的代码缺 BTI 落地指令会被杀——升级到PHP
8.4+(IR 框架已处理)。
第四步:基准测量脚本(没有测量的优化都是玄学)
#!/bin/bash
# bench.sh ——三个层次各测一遍,别只看一个数
PHP=/opt/php/bin/php
for jit in "off" "tracing"; do
echo "=== JIT=$jit ==="
# 层1:纯计算(JIT 收益上限)
$PHP -d opcache.enable_cli=1 -d opcache.jit=$jit -d opcache.jit_buffer_size=128M \
Zend/bench.php | tail -1
# 层2:真实业务压测(wrk 打 Swoole 服务,这才是要汇报的数)
# 先起服务再: wrk -t8 -c256 -d60s --latency http://127.0.0.1:8080/api/hot-path
done
# 层3:perf 看微架构(鲲鹏上确认 iTLB miss、分支误判是否下降)
perf stat -e instructions,cycles,iTLB-load-misses,branch-misses -p $(pidof -s php) sleep 30
2.3 龙芯(loongarch64):JIT 从无到有,三条路线全给出
现状(截至目前的上游状态,立项前务必再核对一次 php-src 和 dstogov/ir 仓库):PHP 8.4 的 IR 框架后端只有 x86_64 和
aarch64;loongarch64 无上游 JIT 后端,opcache.jit 在龙芯上是禁用状态。但注意:opcache 字节码缓存、preload、file_cache
在龙芯全部可用——第一部分的冷启动优化不受影响。另外核对两个周边适配点:Fiber(PH8.1+ 协程栈切换用的手写汇编) 的
loongarch64 支持(Boost.Context 已有 loongarch64 实现,php-src 已跟进合入,用前跑 sapi/cli/php -r 'new
Fiber(fn()=>1);' 验证),以及 Zend 字符串函数的 SIMD 路径(见后)。
路线一(默认路线,性价比最高):不开 JIT,把 JIT 之外的性能全吃满
# 龙芯 3A6000/3C5000 编译参数(LA664/LA464 核心)
CFLAGS="-O2 -march=loongarch64 -mtune=la664 -mlsx" # 3A5000 用 -mtune=la464
# 同样跑上面的两遍 PGO 流程——PG在弱单核 CPU 上相对收益反而更大
再加两个龙芯专属加速点:
1. LSX 向量化补齐:Zend 引擎里 base64_encode、addslashes、UTF-8 校验等热点函数有 SSE2/NEON 手写 SIMD 版本,loongarch64
走的是标量兜底路径。方案:用 LSX(龙芯 128 位向量,指令语义和 SSE2
高度对应,几乎能一比一翻译)补一套。这是比移植整个 JIT
便宜两个数量级、又能立刻上生产的真优化。示例(Zend/zend_string.h 的思路示意):
/* zend 字符串 SIMD 的 loongarch LSX 版补齐(以"查找需要转义的字符"为例)*/
#if defined(__loongarch_sx)
#include <lsxintrin.h>
static zend_always_inline int zend_check_slash_lsx(const char *p) {
__m128i blk = __lsx_vld(p, 0); /* 一次装16字节 */
__m128i slash = __lsx_vreplgr2vr_b('\\'); /* 16个'\' */
__m128i quote = __lsx_vreplgr2vr_b('\'');
__m128i hit = __lsx_vor_v(__lsx_vseq_b(blk, slash), /* 逐字节比较 */
__lsx_vseq_b(blk, quote));
return __lsx_bnz_v(hit); /* 有任一命中? */
}
#endif
2. jemalloc/mimalloc 替换分配器:龙芯上 glibc malloc 较弱,LD_PRELOAD jemalloc(loongarch 已支持)实测 PHP 吞吐能再拿
3%~8%。
路线一的综合效果:PGO + LSX 补齐 + jemalloc + opcache 全开 ≈把"没有 JIT 的龙芯"性能抬升 15%~30%,对 Web
型业务基本追平"该不该有 JIT"的差距。
路线二:跟踪/合作龙芯社区补丁
龙芯软件生态团队(loongnix)长期做解释器/JIT 类移植(LuaJIT、OpenJDK、.NET、V8 的 loongarch
移植都是他们主导或深度参与)。方案:立项前先问龙芯(loongson.cn 生态合作渠道 / loongnix 社区)拿 PHP JIT
移植路线图,有半成品就合作共建,比自己从零干省 80% 成本。这是被无数信创项目验证过的正确姿势:先问龙芯,再自己动手。
路线三:自研 IR 框架 loongarch64 后端(给出完整工程计划)
如果你的业务是计算密集且规模大到值得投入,这是"深度研究"课题该干的事。PHP 8.4 的 JIT 建立在独立的 IR
项目(dstogov/ir)上,移植的战场在 IR 项目里,不在 php-src 里——这是好消息,因为IR 的 aarch64 后端就是现成的参考答案。
工程分解(按 aarch64 后端的文件结构对照):
ir 项目里需要新增/修改的文件:
ir_loongarch64.dasc ←核心:每个 IR 指令 →LoongArch 机器码模板(约 8000 行,工作量大头)
ir_loongarch64.h ←寄存器定义、调用约定描述
dynasm/dasm_loongarch64.lua / .h
←DynASM 的 loongarch 编码器
【捷径】龙芯给 LuaJIT 做的 loongarch DynASM 移植可直接复用改造,
指令编码器不用从零写
ir_emit.c / ir_ra.c ←注册新目标的寄存器分配约束
关键技术决策点(每个都给方案):
┌────────────┬─────────────────────────────┬──────────────────────────────────────────────────────────────────────┐
│ 难点 │ 大白话 │ 方案 │
├────────────┼─────────────────────────────┼──────────────────────────────────────────────────────────────────────┤
│ │ IR │ loongarch 有 32 个 GPR(比 x86_64 的 16 个宽裕),映射比 x86 │
│ 寄存器映射 │ 假设有一堆通用寄存器可分配 │ 后端还舒服:$t0-$t8 做临时、$s0-$s8 做 IR 全局、$a0-$a7 传参,预留 │
│ │ │ $r21 做 JIT 专用(对应 aarch64 后端预留 x27/x28 的做法) │
├────────────┼─────────────────────────────┼──────────────────────────────────────────────────────────────────────┤
│ 大常数加载 │ loongarch 无 x86 那种 64 │ 标准四件套 lu12i.w + ori + lu32i.d + lu52i.d,抄该平台 GCC │
│ │ 位立即数 │ 的做法,DynASM 里写成宏 │
├────────────┼─────────────────────────────┼──────────────────────────────────────────────────────────────────────┤
│ 远跳转 │ 直接跳转指令范围 ±128MB │ JIT buffer ≤128M时单指令 b 够用;超出走"跳板"(veneer),aarch64 │
│ │ │ 后端有现成的 veneer 机制可照搬 │
├────────────┼─────────────────────────────┼──────────────────────────────────────────────────────────────────────┤
│ 内存模型 │ loongarch 是弱内存序 │ opcache 共享内存交互处按 aarch64 后端的屏障插法对齐(dbar 对应 │
│ │ │ dmb),这是最容易埋隐性 bug 的地方,屏障宁多勿少,先求对再求快 │
├────────────┼─────────────────────────────┼──────────────────────────────────────────────────────────────────────┤
│ │ │ ①IR项目自带测试集(ir_test)先全绿;②php-src的 make test 开 JIT │
│ 验证 │ 怎么知道生成的机器码对不对 │ 全量跑;③终极武器:双跑比对——同一请JIT │
│ │ │ 版和解释版各跑一遍比对输出,做成 fuzz 流水线跑一周 │
└────────────┴─────────────────────────────┴──────────────────────────────────────────────────────────────────────┘
投入评估(诚实版):2~3 名懂编译器+懂 loongarch 的工程师,6~12 个月到"能过 php-src 测试集",再 6
个月到"敢上生产"。产出建议直接回馈上游(php-src + ir 项目),一旦进上游,后续 PHP
版本升级的维护成本从"背一辈子私有补丁"变成"社区共同维护"——私有补丁不回上游是信创自研最大的长期陷阱,务必立项时就定下
upstream first 策略。
2.4 JIT ×Serverless 的交叉难题(两个课题在这里相撞)
难题:tracing JIT 需要"热身"(跑几十上百次才编译),而 Serverless 实例可能活不了几分钟——热身完就被缩容了,白编译。
方案矩阵:
1. 长活实例开 JIT,短活实例关 JIT:min-scale≥1的核心函数开 jit=tracing;scale-to-zero 的长尾函数直接
opcache.jit=off(省掉 jit_buffer
内存和编译开销,短生命周期里解释执行反而更划算)。用环境变量注入区分,一份镜像两种姿态。
2. 调低热身阈值:上面给的 jit_hot_loop=32 / jit_hot_func=16 就是为 Serverless 调的,让 JIT 在实例生命周期早期就介入。
3. CRIU 快照带走 JIT 产物:JIT 编译结果在进程内存里,CRIU 快照天然把"已热身的 JIT 代码"一起冻结——快照恢复= 冷启动 +
JIT 热身两个问题一次全解。这是本方案里 CRIU 路线最漂亮的隐藏收益。
4. (前瞻)关注上游 JIT 产物持久化的进展;在它落地前,方案 3 是唯一能"持久化 JIT 热身"的工程手段。
---
第三部分:全场景决策矩阵 + 难题总账
┌──────────────┬─────────────────────────────────────────────┬────────────────────────────────────────────────────┐
│ 场景 │ 冷启动策略 │ JIT 策略 │
├──────────────┼─────────────────────────────────────────────┼────────────────────────────────────────────────────┤
│ 鲲鹏/飞腾 + │ min-scale≥1+ worker 模式 + preload │ tracing + PGO 编译 + 大页 │
│ 核心 API │ │ │
├──────────────┼─────────────────────────────────────────────┼────────────────────────────────────────────────────┤
│ 鲲鹏/飞腾 + │ scale-to-zero + Nydus + file_cache │ JIT off │
│ 长尾函数 │ │ │
├──────────────┼─────────────────────────────────────────────┼────────────────────────────────────────────────────┤
│ 龙芯 + Web │ 同上(全部冷启动优化在龙芯可用) │ 路线一(PGO+LSX+jemalloc),JIT 缺席损失 │
│ 业务 │ │ 5~15%,可接受 │
├──────────────┼─────────────────────────────────────────────┼────────────────────────────────────────────────────┤
│ 龙芯 + │ 预热池 │ 短期:把计算热点用 FFI 调 C(等效手工 │
│ 计算密集 │ │ JIT);中期:路线二问龙芯;长期:路线三自研后端 │
├──────────────┼─────────────────────────────────────────────┼────────────────────────────────────────────────────┤
│ 混合架构集群 │ 函数打 arch 标签,计算密集函数 nodeAffinity │ 各架构各自的 php.ini 由 ConfigMap 按 arch 注入 │
│ │ 锁到鲲鹏节点 │ │
├──────────────┼─────────────────────────────────────────────┼────────────────────────────────────────────────────┤
│ 隔离网 │ 全部方案无外网依赖(镜像/快照/编译全内网) │ PGO 采样数据在内网生产旁路采集 │
└──────────────┴─────────────────────────────────────────────┴────────────────────────────────────────────────────┘
难题总账(一句话方案回顾):拉镜像慢→预热+Nydus;每请求bootstrap→Swooleworker 模式;编译耗时→file_cache
烤进镜像;缩零冷启动→min-scale分级+CRIU;常驻内存串数据→请求级清理纪律+max_request兜底;龙芯无 JIT→PGO+LSX+jemalloc
先追回大半,重度需求走龙芯合作或自研 IR 后端并回馈上游;64K 页/W^X/弱内存序→逐项给了排查点;JIT
热身与短生命周期冲突→分级开关+CRIU连热身一起快照。
落地顺序建议:①先上worker 模式 + preload + file_cache(纯软件改造,一周见效,冷启动砍
80%);②再做镜像预热/Nydus(平台层,两周);③鲲鹏PGO 编译(一周,性能白捡 10%);④CRIU和龙芯 LSX
补齐做技术预研(一个月出可行性报告);⑤龙芯JIT
后端按路线二→路线三顺序推进,作为长期研究课题立项。每一步独立可验收、可写报告。
鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。
更多推荐

所有评论(0)