はじめてのPHP(php-src)
概要
しばらく記事書いてなかったので、リハビリに軽い記事でも書こうかと。
最近PHPerKaigi2025があり、PHPで何かやりたい欲が出てきたので
PHPに関数生やして遊んでみようと思います。
前置き
この記事は、PHPの内部実装について解説する記事ではありません。
PHPに関数を雑に生やして遊ぶだけの記事です。
ちゃんと内部実装を知るには、php-srcのCONTRIBUTING.mdなどを見るのがいいと思います。
PHPの実装
PHPの実装はいくつかあるようですが、The PHP Groupによる公式実装がこちらです。
Go言語で出来た実装(frankenphp)もあります。
ソースからビルドして動かしてみる
clone
とりあえず公式実装(php-src)のmasterをcloneします。
これを書いている時は、PHPのバージョンは8.5.0でした。
git clone -b master --depth 1 https://github.com/php/php-src.git
ビルド
Building PHP source codeやUnix および macOS システムでのソースコードからのインストールを見ながらビルドします。
# Ubuntuの場合
sudo apt install -y \
pkg-config build-essential autoconf bison re2c libxml2-dev libsqlite3-dev
./buildconf
./configure
make -j4
実行
PHPのバイナリは、sapi/cli/以下に出来ます。
sapi/cli/php --version
# PHP 8.5.0-dev (cli) (built: Mar 23 2025 01:29:32) (NTS)
# Copyright (c) The PHP Group
# Zend Engine v4.5.0-dev, Copyright (c) Zend Technologies
sapi/cli/php -r 'echo "hello, world!";'
# hello, world!
関数を追加してみる
PHPに新しい関数を生やしてみます。
N番目の素数を計算するAPI(calc_prime)を作ってみます。
PHPはZend Engineと呼ばれるインタプリタを内蔵しており
Zend Engineから引数を取得したり、Zend Engineに戻り値を返すことでPHPと連携できます。
ext/standard/basic_functions_arginfo.h
// 関数(calc_prime)を定義
ZEND_FUNCTION(calc_prime);
// 引数リストをarginfo_calc_primeという名前で定義
ZEND_BEGIN_ARG_INFO(arginfo_calc_prime, 0)
ZEND_ARG_TYPE_INFO(0, n, IS_LONG, 0)
ZEND_END_ARG_INFO()
// 関数と引数リストを紐付け
static const zend_function_entry ext_functions[] = {
//...
ZEND_FE(calc_prime, arginfo_calc_prime)
ZEND_FE_END
};
ext/standard/basic_functions.c
// 整数Nを与えるとN番目の素数を計算して返却する関数
// 生成AIのPowerを少し借りた
ZEND_FUNCTION(hello_world)
{
// Zend Engineを通して引数を取得
zend_long target_index;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_LONG(target_index)
ZEND_PARSE_PARAMETERS_END();
if (target_index <= 0) {
RETURN_LONG(0);
}
if (target_index == 1) {
RETURN_LONG(2);
}
unsigned int prime_limit = (unsigned int)(target_index * (log(target_index) + log(log(target_index)))) + 10;
unsigned int square_root_limit = (unsigned int)sqrt(prime_limit) + 1;
unsigned int prime_count = 1; // 2をカウント済み
unsigned int latest_prime = 2;
char *is_small_prime = malloc(square_root_limit);
memset(is_small_prime, 1, square_root_limit);
for (unsigned int candidate = 3; candidate * candidate < square_root_limit; candidate += 2) {
if (is_small_prime[candidate]) {
for (unsigned int multiple = candidate * candidate; multiple < square_root_limit; multiple += 2 * candidate) {
is_small_prime[multiple] = 0;
}
}
}
unsigned int *prime_list = malloc(square_root_limit * sizeof(unsigned int));
unsigned int small_prime_count = 0;
prime_list[small_prime_count++] = 2;
for (unsigned int candidate = 3; candidate < square_root_limit; candidate += 2) {
if (is_small_prime[candidate]) {
prime_list[small_prime_count++] = candidate;
}
}
free(is_small_prime);
const unsigned int SEGMENT_SIZE = 1 << 15;
unsigned char *segment_flags = malloc(SEGMENT_SIZE);
for (unsigned int segment_start = 3; segment_start <= prime_limit; segment_start += SEGMENT_SIZE) {
unsigned int segment_end = segment_start + SEGMENT_SIZE - 1;
if (segment_end > prime_limit) {
segment_end = prime_limit;
}
memset(segment_flags, 1, segment_end - segment_start + 1);
for (unsigned int prime_index = 0; prime_index < small_prime_count; prime_index++) {
unsigned int small_prime = prime_list[prime_index];
unsigned int first_multiple = small_prime * small_prime;
if (first_multiple < segment_start) {
first_multiple = (segment_start + small_prime - 1) / small_prime * small_prime;
}
for (unsigned int composite = first_multiple; composite <= segment_end; composite += small_prime) {
segment_flags[composite - segment_start] = 0;
}
}
for (unsigned int offset = 0; offset <= segment_end - segment_start; offset++) {
if (segment_flags[offset]) {
latest_prime = segment_start + offset;
prime_count++;
if (prime_count == target_index) {
free(segment_flags);
free(prime_list);
RETURN_LONG(latest_prime);
}
}
}
}
free(segment_flags);
free(prime_list);
RETURN_FALSE;
}
実行結果
time sapi/cli/php -r 'echo calc_prime(10000000);' 355ms Tue Mar 25 02:05:46 2025
179424673
________________________________________________________
Executed in 359.71 millis fish external
usr time 350.11 millis 0.14 millis 349.98 millis
sys time 7.16 millis 1.07 millis 6.09 millis
10000000番目の素数が約360msで算出できたよ!
ちなみに以下のスペックの私用PC(MBP)を使用しました。
所感
想定していたより簡単に関数生やせたなという印象です。
Zend Engineによる構文解析部分と分離されているおかげだと思います。
ただ、php-srcのビルドは最初手間取りました。(主にautotools関連の依存で)
また、php-srcはgitの履歴を見る限り26年選手で、いくつか気になる点もありました。
巨大リポジトリで依存も多いので、変えるのは非常に難しそうですが...
- cmakeやmeson移行してNinja Build出来たら速そう(しかし労力)
- フォーマッタ欲しい(git-clang-formatで差分だけかかるようにしたらいいかも?)
- オブジェクトファイルはソースとは別ディレクトリに出来たら嬉しい
- etc...
まとめ
PHPはいいぞ(雑)
Discussion