From 2f07bd12c0c77e8985646a9f21a62a91311d82c0 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sun, 23 Aug 2026 11:48:01 +0900 Subject: perf(filesystem): delete directories without spawning rm -rf Composer shells out to `rm -rf` because PHP has no recursive directory removal. Rust has one, and every package installed from a dist archive pays for a removal: the extraction pipeline drops its temporary directory once per package, and uninstalling a package removes its whole tree. The asynchronous path goes through `tokio::fs` rather than `std::fs`, so the walk runs on a blocking thread and the sibling installs the reactor is driving keep making progress, the way they did while the subprocess was working. Installing laravel/laravel (109 packages) from a warm cache drops from 3.85 to 3.19 CPU seconds. The removals run concurrently, so on an idle 16-core machine they never reach the critical path and wall time is unchanged at 1.65 s; pinned to two cores it falls from 2.33 s to 2.20 s. Pruning the 33 dev packages with `install --no-dev`, where whole package trees are removed rather than empty temporary directories, drops from 844 ms to 806 ms even on 16 cores. Windows keeps the `rmdir /S /Q` subprocess, and both platforms keep falling back to `remove_directory_php` when the fast path does not clear the directory. Co-Authored-By: Claude Opus 5 (1M context) --- scripts/bench/install.sh | 57 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100755 scripts/bench/install.sh (limited to 'scripts') diff --git a/scripts/bench/install.sh b/scripts/bench/install.sh new file mode 100755 index 00000000..ef26d273 --- /dev/null +++ b/scripts/bench/install.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash + +set -euo pipefail + +REPO_ROOT="$(git -C "$(dirname "${BASH_SOURCE[0]}")" rev-parse --show-toplevel)" +BIN="$REPO_ROOT/target/release/shirabe" +COMPOSER_BIN="$REPO_ROOT/composer/bin/composer" + +source "$REPO_ROOT/scripts/bench/lib.sh" + +PACKAGE="laravel/laravel" +for arg in "$@"; do + case "$arg" in + --package=*) PACKAGE="${arg#*=}" ;; + *) echo "unknown option: $arg" >&2; exit 2 ;; + esac +done + +if ! command -v hyperfine >/dev/null 2>&1; then + echo "hyperfine not found; install it first (e.g. 'cargo install hyperfine')" >&2 + exit 1 +fi + +cargo build --manifest-path "$REPO_ROOT/Cargo.toml" --release --bin shirabe + +apply_http3_workaround + +OUTDIR="${TMPDIR:-/tmp}/shirabe-bench-install-$$" +mkdir -p "$OUTDIR" +cd "$OUTDIR" + +TARGET_DIR="project-under-test" +PACKAGE_SLUG="${PACKAGE//\//-}" + +# Resolve the dependencies once. What is measured is installing them, not solving them. +"$COMPOSER_BIN" create-project --no-plugins --no-scripts --no-audit --no-interaction \ + "$PACKAGE" "$OUTDIR/template" >/dev/null + +PREPARE_SCRIPT="$OUTDIR/prepare.sh" +cat > "$PREPARE_SCRIPT" <> results: $OUTDIR/results-$PACKAGE_SLUG.json" >&2 +echo ">> $OUTDIR/results-$PACKAGE_SLUG.md" >&2 -- cgit v1.3.1-4-g156e