概述
长期以来PHP作为解释型脚本语言,虽然在Web开发领域占据主导地位,但在性能表现上始终无法与Rust、Golang等编译型语言相提并论。这一局限性使得PHP在高性能计算、系统编程等领域逐渐式微,甚至一度被认为是过时的技术选择。
Swoole-Compiler v4版本推出Native AOT(Ahead-of-Time)编译器,将彻底改写这一现状。AOT编译器突破了PHP传统的解释执行模式,支持将PHP代码直接编译为原生二进制可执行文件。运算性能相比传统PHP解释器提升高达上百倍,性能表现已达到Rust、Golang等现代编译型语言的同一水平线。
这不仅仅是性能的提升,更是PHP语言范式的根本性变革:
从解释执行到原生编译 -
PHP代码可直接生成独立的二进制程序,无需依赖运行时环境从脚本语言到系统级编程 - 性能瓶颈的消除使
PHP具备了开发高性能服务、系统工具的能力
Swoole-Compiler Native AOT编译器不仅为数百万PHP开发者提供了性能优化的终极方案,更重要的是,它为PHP生态注入了新的生命力,在云原生、微服务、高性能计算等现代技术领域占据一席之地。
运行机制
AOT编译器与HHVM、KPHP等项目不同,它并不是PHP的另外一个实现。而是直接使用了ZendPHP的底层库,使用PHPX库作为兼容层,与PHP在ABI层面是完全互通的。但不使用ZendVM的opcode handler以及zend_execute,而是将PHP代码编译为机器指令,与C++和Golang的运行机制一致。因此AOT编译器完全兼容PHP的所有生态:
可调用所有
PHP扩展提供的内置函数、类、常量等同时支持静态编译和动态解释执行,支持
eval、include/require等动态脚本执行支持
Composer,支持autoload,可动态调用其他第三方的composer包提供的函数和类
语法兼容性
AOT编译器支持绝大部分PHP的语法。不过由于AOT是静态编译,某些依赖运行时确定的特性是无法支持的:
不支持
$$语法,局部变量为编译器符号,无法在运行时使用不支持
extract函数,无法运行时创建局部变量不支持
yield/generator生成器语法,建议使用fiber/swoole/swow协程,AOT编译器支持协程不支持多层
break或者continue语法,需要改成goto或try/catch,这个特性只能在虚拟机模式中实现禁止字面量字符串包含
\0,例如$a = "hello \0 world;",与C++不兼容不支持参数数量不匹配的函数调用,例如某个函数的参数是
3个,但是实际运行的代码传入了4个,这在PHP动态执行阶段是允许的,但是AOT编译器无法支持不支持
Property Hook语法不支持动态调用中使用引用,例如
Closure闭包函数的参数是引用类型,在运行时才能确定,在AOT编译器中不支持,需要显式使用refval()函数转为引用
// 运行时才能得到函数的参数和返回值 $fn = getClosure(); // 编译器无法确定参数应该使用值还是引用,默认使用值传递 $fn($a, $b, $c); // $c 将显式地使用引用传递,而不是值 $fn($a, $b, refval($c));编译器
./swoole_compiler -h Swoole-Compiler (AOT) v0.1.0 USAGE: ./bin/compiler.php <file/dir> [options] ARGUMENTS: <file> Input PHP file/directory to compile OPTIONS: -O <level> Optimization level (0-3, default: 0) -p, --profile Enable performance profiling -o, --output <file> Output binary name (default: input basename) -v, --verbose Verbose output -h, --help Show this help message -f, --force Force compile even if cache exists -m, --mode <mode> Compilation mode, -m bin(binary) or -m ext(extension), default: bin -j, --job <num> Number of parallel compilation jobs (default: 4) --no-literal-strings Disable literal strings optimization EXAMPLES: ./bin/compiler.php examples/hello.php ./bin/compiler.php examples/bench.php -O2 ./bin/compiler.php examples/bench.php -O2 ./bin/compiler.php examples/extension -O2 -o myapp -m ext ./bin/compiler.php examples/app.php -O3 -o myapp -vmain 函数
AOT编译器不支持游离代码,所有PHP代码必须放置在function中,因此不支持PHP模版、配置文件,仍然需要动态加载。AOT编译器支持两种模式:
ext模式:将PHP项目编译为PHP扩展,用于PHP-FPM/Apache模式bin模式:将PHP项目编译为二进制可执行程序
bin模式,PHP项目必须提供一个main函数作为入口。
代码
<?php function main(int $argc, array $argv): void { echo "Hello World!\n"; var_dump(PHP_VERSION); }编译
./swoole-compiler examples/code/main.php prepare: examples/code/main.php prepare completed: 1 source files in total convert: examples/code/main.php format: build/examples/code/main.cc cd /home/swoole/workspace/aot && clang-format -i /home/swoole/workspace/aot/build/examples/code/main.cc generate stub file: examples/code/main.php format: build/extension-main.cc cd /home/swoole/workspace/aot && clang-format -i /home/swoole/workspace/aot/build/extension-main.cc Starting parallel compilation with 4 jobs for 5 files Successfully compiled 5 files g++ /home/swoole/workspace/aot/src/Php/../cpp/php_cli_process_title.o /home/swoole/workspace/aot/src/Php/../cpp/ps_title.o /home/swoole/workspace/aot/src/Php/../cpp/main.o /home/swoole/workspace/aot/build/examples/code/main.o /home/swoole/workspace/aot/build/extension-main.o -o main $(php-config --includes) -I ~/workspace/projects/phpx/include -I /home/swoole/workspace/aot/build/include -I /home/swoole/workspace/aot/src/cpp -O0 -g -Wall -L $(php-config --prefix)/lib -L ~/workspace/projects/phpx/lib -lphpx -lphp编译完成后,将生成一个main的二进制可执行文件,不包含PHP源代码,而是直接执行机器指令。
file main main: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=****, for GNU/Linux 3.2.0, with debug_info, not stripped ll main -h -rwxrwxr-x 1 swoole swoole 377K 4月 22 17:16 main文件的尺寸为377K,可使用upx或者strip压缩裁剪。
./strip main ll main -h -rwxrwxr-x 1 swoole swoole 39K 4月 22 17:20 main执行
./main Hello World! string(6) "8.4.14"运算性能大幅提升
与PHP的ZendVM+JIT执行机制不同,AOT编译器是将PHP代码编译为机器指令,在密集运算方面得到了数百倍的提升。
斐波那契数列
function fib(int $n): int { if ($n == 1 || $n == 2) { return1; } else { return fib($n - 1) + fib($n - 2); } } function main(int $argc, array $argv): void { $n = $argv[2]; $begin = microtime(true); echo fib($n) . "\n"; echo"Time: " . (microtime(true) - $begin) . "\n"; }执行结果:
$ ./cli.php examples/fib.php 40 102334155 Time: 14.816216945648193 $ ./fib examples/fib.php 40 102334155 Time: 0.10961484909057617cli.php是通过PHP ZendVM执行脚本,仅用于进行性能对比fib使用了O3编译优化,命令是:./swoole_compiler examples/fib.php -O3
#!/usr/bin/env php <?php include $argv[1]; main($argc, $argv);斐波那契数列的测试中,AOT编译器相比PHP提升了150倍。
PI 值计算(Leibniz 公式)
use native_types; function main() { ini_set("precision", 17); $rounds = (int) file_get_contents("./rounds.txt", true); $stop = $rounds + 2; var_dump($stop); $begin = microtime(true); $x = 1.0; $pi = 1.0; for ($i = 2; $i <= $stop; $i++) { $x = -1.0 + 2.0 * ($i & 0x1); $pi += $x / (2 * $i - 1); } $pi *= 4.0; print $pi . "\ntime: " . (microtime(true) - $begin) . "\n"; }./cli.php examples/pi.php int(100000002) 3.141592643589326 time: 6.5242888927459717 ./pi examples/pi.php int(100000002) 3.141592643589326 time: 0.09414982795715332AOT编译器提升了72倍。
JIT测试
zend_extension=opcache opcache.enable_cli=On opcache.jit=On测试结果
./cli.php examples/fib.php 40 102334155 Time: 2.3740291595458984 ./cli.php examples/pi.php int(100000002) 3.141592643589326 time: 2.2408719062805176启用JIT后,两个测试程序的性能得到了数倍提升,但相比AOT编译器,依然相差了两个数量级。
在PHP自带的bench.php和micro_bench.php部分测试开启opcache+JIT的性能比AOT编译器更好,主要原因是opcache拥有死代码消除优化器,没有实际作用的代码会被移除,导致部分PHP逻辑并未执行,无法真正比较执行性能,因此暂时不作为对比脚本。
use native_types
PHP的Int类型在溢出时会自动转浮点,例如INT_MAX + 1会丢失精度转为浮点,10➗️3无法整除时自动转为浮点。
PHP为了支持这些特性牺牲了较多性能,但实际项目中几乎很少使用。使用整型时为精确计算,不应该转为浮点丢失精度,若非精确计算则应该直接使用浮点,而不是在运行时进行判断、检测和转换。
为了兼容已有的PHP代码,默认AOT编译器不会将整数声明为Native类型,保持了溢出自动转换的特性。在密集运算场景中,性能较差。在源文件中使用use native_types,则会告诉编译器,将整数、浮点型声明为原生类型,性能将大幅提升,但在溢出时则会丢弃精度,而不是转为浮点。
use native_types; function main() { $a = 10; $b = $a / 3; // $b 的值将是 3 ,而不是 3.333... }全新的 C/C++ 互调用设计
在PHP项目中若需要调用C++的类或者函数,通常需要编写一个PHP扩展,或者使用FFI。AOT编译器提供了全新的C/C++互调用设计,允许在PHP代码中直接调用C++函数,编译器会将C++的.cpp文件与.php文件一起编译并连接为一个可执行文件。
素数计算
function sieveOfEratosthenes(int $limit) { if ($limit < 2) return []; // 初始化布尔数组,索引代表数字,值代表是否为素数 $isPrime = array_fill(0, $limit + 1, true); $isPrime[0] = false; $isPrime[1] = false; // 0 和 1 不是素数 for ($i = 2; $i * $i <= $limit; $i++) { if ($isPrime[$i]) { // 标记 i 的所有倍数为非素数 for ($j = $i * $i; $j <= $limit; $j += $i) { $isPrime[$j] = false; } } } // 收集所有素数 $primes = []; for ($num = 2; $num <= $limit; $num++) { if ($isPrime[$num]) { $primes[] = $num; } } return $primes; } function main() { global $argv; $n = isset($argv[2]) ? (int)$argv[2] : 10000000; $begin = microtime(true); // 参数校验 if ($n < 2) { fwrite(STDERR, "请输入一个大于等于2的整数作为上限。\n"); exit(1); } $primes = sieveOfEratosthenes($n); var_dump(count($primes)); var_dump(microtime(true) - $begin); }在素数计算的程序中,PHP只有数组可以作为容器,数组的元素存储的类型为bool,PHP即使存储一个bit的bool值,也至少需要16个字节,存储10亿个数字,需要40 GB+内存。如果能够直接使用C++的std::vector<bool>则仅需120M字节。
C++的std::vector<bool>是一个bitmap,占用的内存极低
但在PHP代码中不能直接调用C++函数,而编写一个PHP扩展项目,又是一件非常麻烦的事情。在AOT编译器中可以直接调用C++代码,可以轻松解决此问题。
代码修改
function sieveOfEratosthenes(int $limit) { if ($limit < 2) return []; // 初始化布尔数组,索引代表数字,值代表是否为素数 $isPrime = vector_new($limit + 1, true); // 0 和 1 不是素数 vector_set($isPrime, 0, false); vector_set($isPrime, 1, false); for ($i = 2; $i * $i <= $limit; $i++) { if (vector_get($isPrime, $i)) { // 标记 i 的所有倍数为非素数 for ($j = $i * $i; $j <= $limit; $j += $i) { vector_set($isPrime, $j, false); } } } // 收集所有素数 $primes = []; for ($num = 2; $num <= $limit; $num++) { if (vector_get($isPrime, $num)) { $primes[] = $num; } } return $primes; }新的代码使用vector_set()和vector_get()替代数组来管理数字。接下来需要编写一个vector.stub.php存根文件来声明函数。
function vector_new(int $size, bool $init = false): mixed{} function vector_get(mixed $vector, int $offset): bool {} function vector_set(mixed $vector, int $offset, bool $value): void {}.stub.php文件只有函数的声明,但没有实现,具体实现的代码将在.cc文件中编写。
#include <phpx.h> usingnamespace php; class VectorBox :public Box { public: std::vector<bool> vec; VectorBox(size_t size, bool init) { vec.resize(size, init); } void checkOffset(Int offset) { if (offset >= vec.size()) { throwError("index[%ld] is out of range()", offset); } } }; var php_vector_new(Int size, Bool init) { return {new VectorBox(size, init)}; } Bool php_vector_get(var box, Int offset) { auto vecbox = box.toBox<VectorBox>(); vecbox->checkOffset(offset); return vecbox->vec.at(offset); } void php_vector_set(var box, Int offset, Bool value) { auto vecbox = box.toBox<VectorBox>(); vecbox->checkOffset(offset); vecbox->vec.at(offset) = value; }
C++代码的编写可参考PHPX相关文档
导出C++函数为PHP函数,在PHP代码中调用,需要满足以下条件:
必须以
php_为前缀必须以
PHP类型作为参数和返回值需要在
.stub.php文件中声明该函数
类型映射关系表:
PHP 类型 | C++ 类型 |
|---|---|
int | php::Int |
bool | php::Bool |
array | php::Array |
mixed | php::Var |
float | php::Float |
string | php::Str |
object | php::Object |
resource | php::Resource |
void | void |
例如在.stub.php中声明一个函数
function fn_test(int $a, int $b): string {}等价于
php::Str php_fn_test(php::Int a, php::Int b) { }在其他的PHP文件中就直接调用该函数:
echo fn_test(100, 999);除了函数之外,也可以实现PHP类,方法、静态方法、属性、常量、静态属性在stub文件中声明,在C++文件中只实现方法和静态方法。
命名空间和类名使用双下划线(
__)分割作为前缀名称需要全部转为小写
class ClassFoo { protected string $prop; public function __construct(string $name); public function bar(int $a): int; }void php_classfoo____construct(php::Object &this_, php::Str name) {} php::Int php_classfoo__bar(php::Object &this_, php::Str name) {}类方法函数的第一个参数是
this_,表示当前对象,若是静态方法,则第一个参数为NULL可使用
this_.attr()读写对象属性,使用this_.call()调用对象方法
实际项目测试
本次AOT选择了Workerman作为测试项目。Workerman项目的代码需要少量修改才能够通过编译。包括游离代码处理,retval()修改等。可根据编译器的错误提示修改代码实现适配。
除此之外还需要编写一个main.php文件作为入口:
use Workerman\Protocols\Http\Session; useWorkerman\Worker; function main() { require__DIR__ . '/../functions.php'; $http_worker = new Worker('http://0.0.0.0:2345'); $http_worker->count = 1; $http_worker->onMessage = function ($connection, $request) { $connection->send("Hello World"); }; Session::init(); Worker::runAll(); }./swoole_compiler projects/workerman/src/ -o workerman运行
./workerman start Workerman[worker.php] start in DEBUG mode -------------------------------------------- WORKERMAN --------------------------------------------- Workerman/5.1.9 PHP/8.4.14 (JIT off) Linux/6.8.0-107-generic --------------------------------------------- WORKERS ---------------------------------------------- event-loop proto user worker listen count state select tcp swoole none http://0.0.0.0:2345 1 [OK] ---------------------------------------------------------------------------------------------------- Press Ctrl+C to stop. Start success.curl http://127.0.0.1:2345 Hello WorldPython 互调用支持
AOT编译器将支持在PHP代码中直接调用Python语言的特性。
use sys; useos; usenumpyasnp; function np() { print(np::$module); print(np::version->full_version); } function main() { var_dump(sys::api_version); var_dump(os::getpid()); var_dump(os::environ->get("PATH")); }此特性在预览版中尚未实现,将在测试版中提供
版本计划
虽然识沃团队已经对AOT编译器进行了大量内部测试和优化,但项目本身依然处于产品打磨期,未来依然有很长的路要走。AOT编译器整体的发版计划如下:
预览版:在
2026.5.1前发布,仅用于功能体验,不可用于生产环境测试版:在
2026.10.1前发布,解决绝大部分问题,可用于非核心项目正式版:在
2027.5.1前发布,可用于正式的生产环境
识沃团队将同时维护3.2和4.0两个版本,3.2版本依然是围绕着PHP语言的支持迭代,4.0版本则将逐步发展为一门与PHP语言相似的全新静态编译语言。
Swoole-Compiler 版本 | 技术方向 | PHP 版本 |
|---|---|---|
v3.2 | Opcode字节码加密与混淆 | 7.2~8.4 |
v4.0 | AOT Native编译器 | 8.2 ~ 8.5 |