Skip to content

Add native Rust-based MySQL parser extension #70

Add native Rust-based MySQL parser extension

Add native Rust-based MySQL parser extension #70

Workflow file for this run

name: Native AST Walk Perf
on:
push:
paths:
- '.github/workflows/native-ast-perf.yml'
- 'packages/mysql-on-sqlite/src/mysql/native/**'
- 'packages/mysql-on-sqlite/tests/tools/run-native-ast-walk-benchmark.php'
- 'packages/php-ext-wp-mysql-parser/**'
pull_request:
paths:
- '.github/workflows/native-ast-perf.yml'
- 'packages/mysql-on-sqlite/src/mysql/native/**'
- 'packages/mysql-on-sqlite/tests/tools/run-native-ast-walk-benchmark.php'
- 'packages/php-ext-wp-mysql-parser/**'
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
perf:
name: Native AST walk benchmark
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
coverage: none
- name: Set up Rust
uses: dtolnay/rust-toolchain@stable
- name: Install native build dependencies
run: |
sudo apt-get update
sudo apt-get install -y libclang-dev
echo "PHP_CONFIG=$(command -v php-config)" >> "$GITHUB_ENV"
LIBCLANG_SO="$(find /usr/lib -name 'libclang.so*' | head -n 1)"
echo "LIBCLANG_PATH=$(dirname "$LIBCLANG_SO")" >> "$GITHUB_ENV"
- name: Install Composer dependencies (root)
uses: ramsey/composer-install@v3
with:
ignore-cache: "yes"
composer-options: "--optimize-autoloader"
- name: Install Composer dependencies (mysql-on-sqlite)
uses: ramsey/composer-install@v3
with:
working-directory: packages/mysql-on-sqlite
ignore-cache: "yes"
composer-options: "--optimize-autoloader"
- name: Download MySQL test query corpus
working-directory: packages/mysql-on-sqlite
run: bash tests/tools/mysql-download-tests.sh || true
- name: Build parser extension (release)
run: cargo build --release
working-directory: packages/php-ext-wp-mysql-parser
- name: Locate built extension
run: |
EXT="$GITHUB_WORKSPACE/packages/php-ext-wp-mysql-parser/target/release/libwp_mysql_parser.so"
test -f "$EXT" || { echo "Extension not built at $EXT"; exit 1; }
echo "NATIVE_EXT=$EXT" >> "$GITHUB_ENV"
- name: Benchmark — pure-PHP path (parse only)
working-directory: packages/mysql-on-sqlite
run: |
php tests/tools/run-native-ast-walk-benchmark.php --no-walk | tee php-parse-only.txt
- name: Benchmark — pure-PHP path (walk)
working-directory: packages/mysql-on-sqlite
run: |
php tests/tools/run-native-ast-walk-benchmark.php | tee php-walk.txt
- name: Benchmark — native path (parse only)
working-directory: packages/mysql-on-sqlite
run: |
php -d extension="$NATIVE_EXT" tests/tools/run-native-ast-walk-benchmark.php --no-walk | tee native-parse-only.txt
- name: Benchmark — native path (walk, with identity cache)
working-directory: packages/mysql-on-sqlite
run: |
php -d extension="$NATIVE_EXT" tests/tools/run-native-ast-walk-benchmark.php | tee native-walk.txt
- name: Check out baseline (PR base, no identity cache)
run: |
git fetch --no-tags --depth=1 origin codex/native-lazy-ast-facade
git worktree add ../baseline FETCH_HEAD
- name: Install Composer dependencies (baseline mysql-on-sqlite)
uses: ramsey/composer-install@v3
with:
working-directory: ../baseline/packages/mysql-on-sqlite
ignore-cache: "yes"
composer-options: "--optimize-autoloader"
- name: Build baseline parser extension (release)
run: cargo build --release
working-directory: ../baseline/packages/php-ext-wp-mysql-parser
- name: Stage benchmark + corpus into baseline
run: |
mkdir -p ../baseline/packages/mysql-on-sqlite/tests/mysql/data
cp packages/mysql-on-sqlite/tests/tools/run-native-ast-walk-benchmark.php \
../baseline/packages/mysql-on-sqlite/tests/tools/
cp packages/mysql-on-sqlite/tests/mysql/data/*.csv \
../baseline/packages/mysql-on-sqlite/tests/mysql/data/ 2>/dev/null || true
- name: Benchmark — baseline native path (walk, no identity cache)
working-directory: ../baseline/packages/mysql-on-sqlite
run: |
BASE_EXT="$(realpath ../../packages/php-ext-wp-mysql-parser/target/release/libwp_mysql_parser.so)"
php -d extension="$BASE_EXT" tests/tools/run-native-ast-walk-benchmark.php \
| tee "$GITHUB_WORKSPACE/packages/mysql-on-sqlite/baseline-native-walk.txt"
- name: Benchmark — baseline native path (parse only)
working-directory: ../baseline/packages/mysql-on-sqlite
run: |
BASE_EXT="$(realpath ../../packages/php-ext-wp-mysql-parser/target/release/libwp_mysql_parser.so)"
php -d extension="$BASE_EXT" tests/tools/run-native-ast-walk-benchmark.php --no-walk \
| tee "$GITHUB_WORKSPACE/packages/mysql-on-sqlite/baseline-native-parse-only.txt"
# Hit-heavy scenarios — these are where the per-AST identity cache is
# supposed to win. The baseline reallocates wrappers on every accessor
# call, while the PR reuses them. Run on both to make the gap visible.
- name: Benchmark — native rewalk x10 (this PR)
working-directory: packages/mysql-on-sqlite
run: |
php -d extension="$NATIVE_EXT" tests/tools/run-native-ast-walk-benchmark.php --mode=rewalk --repeat=10 \
| tee native-rewalk.txt
- name: Benchmark — baseline rewalk x10
working-directory: ../baseline/packages/mysql-on-sqlite
run: |
BASE_EXT="$(realpath ../../packages/php-ext-wp-mysql-parser/target/release/libwp_mysql_parser.so)"
php -d extension="$BASE_EXT" tests/tools/run-native-ast-walk-benchmark.php --mode=rewalk --repeat=10 \
| tee "$GITHUB_WORKSPACE/packages/mysql-on-sqlite/baseline-native-rewalk.txt"
- name: Benchmark — native reread x20 (this PR)
working-directory: packages/mysql-on-sqlite
run: |
php -d extension="$NATIVE_EXT" tests/tools/run-native-ast-walk-benchmark.php --mode=reread --repeat=20 \
| tee native-reread.txt
- name: Benchmark — baseline reread x20
working-directory: ../baseline/packages/mysql-on-sqlite
run: |
BASE_EXT="$(realpath ../../packages/php-ext-wp-mysql-parser/target/release/libwp_mysql_parser.so)"
php -d extension="$BASE_EXT" tests/tools/run-native-ast-walk-benchmark.php --mode=reread --repeat=20 \
| tee "$GITHUB_WORKSPACE/packages/mysql-on-sqlite/baseline-native-reread.txt"
- name: Benchmark — native subtree x5 (this PR)
working-directory: packages/mysql-on-sqlite
run: |
php -d extension="$NATIVE_EXT" tests/tools/run-native-ast-walk-benchmark.php --mode=subtree --repeat=5 \
| tee native-subtree.txt
- name: Benchmark — baseline subtree x5
working-directory: ../baseline/packages/mysql-on-sqlite
run: |
BASE_EXT="$(realpath ../../packages/php-ext-wp-mysql-parser/target/release/libwp_mysql_parser.so)"
php -d extension="$BASE_EXT" tests/tools/run-native-ast-walk-benchmark.php --mode=subtree --repeat=5 \
| tee "$GITHUB_WORKSPACE/packages/mysql-on-sqlite/baseline-native-subtree.txt"
- name: Summarize
if: always()
working-directory: packages/mysql-on-sqlite
run: |
extract() {
# Pull a numeric field (e.g. duration=1.23s) from a benchmark
# output line. Returns "n/a" if missing.
local file="$1" key="$2"
[ -f "$file" ] || { echo "n/a"; return; }
grep -oE "${key}=[^ ]+" "$file" | head -1 | cut -d= -f2 || echo "n/a"
}
{
echo '### Native AST walk perf'
echo
echo '| scenario | result |'
echo '|---|---|'
for f in php-parse-only.txt php-walk.txt native-parse-only.txt native-walk.txt baseline-native-parse-only.txt baseline-native-walk.txt native-rewalk.txt baseline-native-rewalk.txt native-reread.txt baseline-native-reread.txt native-subtree.txt baseline-native-subtree.txt; do
[ -f "$f" ] || continue
line="$(cat "$f")"
echo "| ${f%.txt} | \`$line\` |"
done
echo
echo '### Identity-cache cost (native walk: with cache vs PR base without)'
echo
echo '| metric | with cache (this PR) | baseline | delta |'
echo '|---|---|---|---|'
for key in duration qps peak_mem walked_nodes; do
with="$(extract native-walk.txt "$key")"
base="$(extract baseline-native-walk.txt "$key")"
echo "| $key | $with | $base | — |"
done
} >> "$GITHUB_STEP_SUMMARY"
- name: Upload raw output
if: always()
uses: actions/upload-artifact@v4
with:
name: native-ast-perf-${{ github.run_id }}
path: packages/mysql-on-sqlite/*.txt
if-no-files-found: warn