Repository navigation
GPUI frame benchmarks: interleaved A/B per platform, scene-equivalence gate, Cap editor A/B #3886
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
b128989
Add GPUI frame benchmarks with interleaved A/B runs on macOS, Windows…
richiemcilroy 944b5eb
Bound Criterion samples per benchmark round
richiemcilroy 7c8eb20
Add an interleaved A/B run of the Cap editor's draw benchmark
richiemcilroy de368f5
Add a callgrind profile of the GPUI frame benchmarks
richiemcilroy 138ebad
Build both benchmark sides from one checkout path
richiemcilroy 02c5a76
Compare the editor benchmark by median draw over five rounds
richiemcilroy e0b4c01
Resolve benchmark executables before changing directory; allow choosi…
richiemcilroy e519b4d
Report each benchmark size on its own, and compare per-frame draw time
richiemcilroy 5e28ae0
Profile 15 s of frames per benchmark
richiemcilroy 1825c6c
Let local runs pick the benchmark toolchain (GPUI_BENCH_TOOLCHAIN, de…
richiemcilroy 05126c6
Gate GPUI patch changes on identical scenes, frame by frame
richiemcilroy e3a8c7f
Scene gate: cover atlas pixels and missing frames; keep fork PRs off …
richiemcilroy e9fad53
Fix relative bench output paths; trim comments to non-obvious context
richiemcilroy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| name: GPUI app benchmark | ||
|
|
||
| on: | ||
| pull_request: | ||
| paths: | ||
| - "apps/desktop-gpui/patches/**" | ||
| - "scripts/gpui-app-bench.py" | ||
| - ".github/workflows/gpui-app-bench.yml" | ||
| push: | ||
| branches: | ||
| - "gpui-perf/**" | ||
| workflow_dispatch: | ||
| inputs: | ||
| base: | ||
| description: "Ref whose patch stack is the baseline (default: the merge base with main)" | ||
| default: "" | ||
| rounds: | ||
| description: "Interleaved rounds per side" | ||
| default: "5" | ||
|
|
||
| concurrency: | ||
| group: gpui-app-bench-${{ github.event.pull_request.number || github.ref }} | ||
| cancel-in-progress: true | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| editor: | ||
| name: Editor draw benchmark (macOS) | ||
| # Benchmarks build GPUI twice and run for up to two hours, so pull requests | ||
| # from forks do not start them; maintainers can run them with workflow_dispatch. | ||
| if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository | ||
| runs-on: blacksmith-6vcpu-macos-15 | ||
| timeout-minutes: 150 | ||
| env: | ||
| CARGO_PROFILE_DEV_DEBUG: "0" | ||
| TARGET: aarch64-apple-darwin | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 | ||
| persist-credentials: false | ||
|
|
||
| - name: Checkout the reference recording | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| repository: CapSoftware/cap-performance-fixtures | ||
| path: performance-fixtures | ||
| persist-credentials: false | ||
|
|
||
| - name: Rust setup | ||
| uses: dtolnay/rust-toolchain@1.95.0 | ||
| with: | ||
| targets: ${{ env.TARGET }} | ||
|
|
||
| - uses: ./.github/actions/setup-rust-cache | ||
| with: | ||
| target: ${{ env.TARGET }} | ||
| prefix-key: v1-rust-gpui | ||
| save-cache: "false" | ||
| workspaces: apps/desktop-gpui -> target | ||
|
|
||
| - name: Run setup | ||
| shell: bash | ||
| run: node scripts/setup.js | ||
| env: | ||
| RUST_TARGET_TRIPLE: ${{ env.TARGET }} | ||
|
|
||
| - name: Prepare head and base GPUI checkouts | ||
| shell: bash | ||
| env: | ||
| BASE_INPUT: ${{ inputs.base }} | ||
| PR_BASE: ${{ github.event.pull_request.base.sha }} | ||
| run: | | ||
| set -euo pipefail | ||
| node scripts/sync-desktop-versions.mjs | ||
| ./scripts/prepare-gpui-dependency.sh | ||
| base="${BASE_INPUT:-}" | ||
| if [[ -z "$base" ]]; then | ||
| base="$(git merge-base HEAD "${PR_BASE:-origin/main}")" | ||
| fi | ||
| echo "Head $(git rev-parse --short HEAD), base patch stack from $(git rev-parse --short "$base")" | tee -a "$GITHUB_STEP_SUMMARY" | ||
| git worktree add --detach "$RUNNER_TEMP/base/cap" "$base" | ||
| (cd "$RUNNER_TEMP/base/cap" && ./scripts/prepare-gpui-dependency.sh) | ||
|
|
||
| - name: Build head and base | ||
| shell: bash | ||
| run: | | ||
| set -euo pipefail | ||
| zed="$(cd .. && pwd)/zed-cap" | ||
| bin_dir="$GITHUB_WORKSPACE/apps/desktop-gpui/target/$TARGET/debug" | ||
| build() { | ||
| # The app patches gpui to ../zed-cap, so each build swaps the | ||
| # checkout in. Cargo decides freshness of path dependencies by | ||
| # mtime, so touch the swapped-in sources to force a rebuild. | ||
| find "$zed/crates" -type f -name '*.rs' -exec touch {} + | ||
| cargo +1.95.0 build --manifest-path apps/desktop-gpui/Cargo.toml --locked \ | ||
| --target "$TARGET" --bin cap-gpui | ||
| cp "$bin_dir/cap-gpui" "$bin_dir/cap-gpui-$1" | ||
| } | ||
| build head | ||
| mv "$zed" "$zed-head" | ||
| mv "$RUNNER_TEMP/base/zed-cap" "$zed" | ||
| build base | ||
| mv "$zed" "$RUNNER_TEMP/base/zed-cap" | ||
| mv "$zed-head" "$zed" | ||
| for frameworks in "$bin_dir/Frameworks" "$GITHUB_WORKSPACE/apps/desktop-gpui/target/$TARGET/Frameworks"; do | ||
| mkdir -p "$frameworks" | ||
| ln -sfn "$GITHUB_WORKSPACE/target/Frameworks/Spacedrive.framework" "$frameworks/Spacedrive.framework" | ||
| done | ||
| if cmp -s "$bin_dir/cap-gpui-head" "$bin_dir/cap-gpui-base"; then | ||
| echo "Head and base builds are identical (no GPUI change)." | tee -a "$GITHUB_STEP_SUMMARY" | ||
| fi | ||
|
|
||
| - name: Keep the display awake | ||
| run: | | ||
| sudo pmset -a displaysleep 0 sleep 0 || true | ||
| caffeinate -u -t 2 | ||
|
|
||
| - name: Measure | ||
| shell: bash | ||
| env: | ||
| ROUNDS: ${{ inputs.rounds || '5' }} | ||
| run: | | ||
| bin_dir="$GITHUB_WORKSPACE/apps/desktop-gpui/target/$TARGET/debug" | ||
| python3 scripts/gpui-app-bench.py \ | ||
| --base "$bin_dir/cap-gpui-base" \ | ||
| --head "$bin_dir/cap-gpui-head" \ | ||
| --fixture "$GITHUB_WORKSPACE/performance-fixtures/reference-recording.cap" \ | ||
| --out "$RUNNER_TEMP/app-bench" \ | ||
| --rounds "$ROUNDS" \ | ||
| --label macOS \ | ||
| --summary "$GITHUB_STEP_SUMMARY" | ||
|
|
||
| - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 | ||
| if: always() | ||
| with: | ||
| name: gpui-app-bench-macos | ||
| path: ${{ runner.temp }}/app-bench | ||
| if-no-files-found: ignore | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,254 @@ | ||
| name: GPUI frame benchmarks | ||
|
|
||
| on: | ||
| pull_request: | ||
| paths: | ||
| - "apps/desktop-gpui/patches/**" | ||
| - "apps/desktop-gpui/gpui-benches/**" | ||
| - "scripts/prepare-gpui-dependency.sh" | ||
| - "scripts/gpui-bench-*" | ||
| - "scripts/gpui-scene-digest.py" | ||
| - ".github/workflows/gpui-bench.yml" | ||
| push: | ||
| branches: | ||
| - "gpui-perf/**" | ||
| workflow_dispatch: | ||
| inputs: | ||
| base: | ||
| description: "Ref whose patch stack is the baseline (default: the merge base with main)" | ||
| default: "" | ||
| rounds: | ||
| description: "Interleaved rounds per side" | ||
| default: "5" | ||
|
|
||
| concurrency: | ||
| group: gpui-bench-${{ github.event.pull_request.number || github.ref }} | ||
| cancel-in-progress: true | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| bench: | ||
| name: GPUI benchmarks (${{ matrix.settings.name }}) | ||
| # Benchmarks build GPUI twice and run for up to two hours, so pull requests | ||
| # from forks do not start them; maintainers can run them with workflow_dispatch. | ||
| if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository | ||
| timeout-minutes: 150 | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| settings: | ||
| - name: macOS | ||
| slug: macos | ||
| runner: blacksmith-6vcpu-macos-15 | ||
| - name: Windows | ||
| slug: windows | ||
| runner: blacksmith-8vcpu-windows-2025 | ||
| - name: Linux | ||
| slug: linux | ||
| runner: blacksmith-8vcpu-ubuntu-2404 | ||
| runs-on: ${{ matrix.settings.runner }} | ||
| env: | ||
| CARGO_TARGET_DIR: ${{ github.workspace }}/../gpui-bench-target | ||
| CARGO_PROFILE_BENCH_DEBUG: "false" | ||
| CARGO_INCREMENTAL: "0" | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 | ||
| persist-credentials: false | ||
|
|
||
| - name: Install Linux dependencies | ||
| if: ${{ runner.os == 'Linux' }} | ||
| run: | | ||
| sudo apt-get update | ||
| sudo apt-get install -y --no-install-recommends build-essential pkg-config clang libclang-dev \ | ||
| libx11-dev libx11-xcb-dev libxcb1-dev libxkbcommon-dev libxkbcommon-x11-dev libwayland-dev \ | ||
| libfontconfig1-dev libfreetype-dev libvulkan1 mesa-vulkan-drivers libssl-dev libasound2-dev \ | ||
| libzstd-dev fonts-dejavu-core | ||
|
|
||
| - name: Allow long paths in Zed's git dependencies | ||
| if: ${{ runner.os == 'Windows' }} | ||
| run: git config --global core.longpaths true | ||
|
|
||
| - name: Rust setup | ||
| uses: dtolnay/rust-toolchain@1.95.0 | ||
|
|
||
| - uses: actions/setup-python@v5 | ||
| with: | ||
| python-version: "3.12" | ||
|
|
||
| - name: Prepare head and base GPUI checkouts | ||
| shell: bash | ||
| env: | ||
| BASE_INPUT: ${{ inputs.base }} | ||
| PR_BASE: ${{ github.event.pull_request.base.sha }} | ||
| run: | | ||
| set -euo pipefail | ||
| ./scripts/prepare-gpui-dependency.sh | ||
| base="${BASE_INPUT:-}" | ||
| if [[ -z "$base" ]]; then | ||
| base="$(git merge-base HEAD "${PR_BASE:-origin/main}")" | ||
| fi | ||
| echo "Head $(git rev-parse --short HEAD), base patch stack from $(git rev-parse --short "$base")" | tee -a "$GITHUB_STEP_SUMMARY" | ||
| git worktree add --detach "$RUNNER_TEMP/base/cap" "$base" | ||
| (cd "$RUNNER_TEMP/base/cap" && ./scripts/prepare-gpui-dependency.sh) | ||
| echo "ZED_HEAD=$(cd ../zed-cap && pwd)" >> "$GITHUB_ENV" | ||
| echo "ZED_LOCK_HASH=$(python -c 'import hashlib,sys; print(hashlib.sha256(open(sys.argv[1],"rb").read()).hexdigest()[:16])' ../zed-cap/Cargo.lock)" >> "$GITHUB_ENV" | ||
| echo "ZED_BASE=$RUNNER_TEMP/base/zed-cap" >> "$GITHUB_ENV" | ||
|
|
||
| - name: Cargo cache | ||
| uses: actions/cache@v4 | ||
| with: | ||
| path: | | ||
| ~/.cargo/registry/index/ | ||
| ~/.cargo/registry/cache/ | ||
| ~/.cargo/git/db/ | ||
| ${{ github.workspace }}/../gpui-bench-target | ||
| key: gpui-bench-v1-${{ runner.os }}-${{ env.ZED_LOCK_HASH }}-${{ github.run_id }} | ||
| restore-keys: | | ||
| gpui-bench-v1-${{ runner.os }}-${{ env.ZED_LOCK_HASH }}- | ||
| gpui-bench-v1-${{ runner.os }}- | ||
|
|
||
| # Both sides build from the same checkout path, swapped in turn. Paths | ||
| # end up in symbol hashes and panic locations, so building them from two | ||
| # paths gives two code layouts that differ by a few percent with | ||
| # identical sources. Cargo judges path dependencies by mtime, so the | ||
| # swapped-in sources are touched to force the rebuild. | ||
| - name: Build benchmarks | ||
| shell: bash | ||
| run: | | ||
| set -euo pipefail | ||
| build() { | ||
| find "$ZED_HEAD/crates" -type f -name '*.rs' -exec touch {} + | ||
| ./scripts/gpui-bench-build.sh "$ZED_HEAD" "$RUNNER_TEMP/bench/$1" | ||
| } | ||
| mv "$ZED_HEAD" "$ZED_HEAD.head" | ||
| mv "$ZED_BASE" "$ZED_HEAD" | ||
| build base | ||
| mv "$ZED_HEAD" "$ZED_BASE" | ||
| mv "$ZED_HEAD.head" "$ZED_HEAD" | ||
| build head | ||
| if cmp -s "$RUNNER_TEMP/bench/base/frame"* "$RUNNER_TEMP/bench/head/frame"*; then | ||
| echo "Base and head benchmark builds are identical." | tee -a "$GITHUB_STEP_SUMMARY" | ||
| fi | ||
|
|
||
| - name: Measure | ||
| shell: bash | ||
| env: | ||
| ROUNDS: ${{ inputs.rounds || '5' }} | ||
| run: | | ||
| python scripts/gpui-bench-compare.py \ | ||
| --base "$RUNNER_TEMP/bench/base" \ | ||
| --head "$RUNNER_TEMP/bench/head" \ | ||
| --out "$RUNNER_TEMP/bench/runs" \ | ||
| --rounds "$ROUNDS" \ | ||
| --label "${{ matrix.settings.name }}" \ | ||
| --summary "$GITHUB_STEP_SUMMARY" | ||
|
|
||
| # Only macOS has a headless renderer that sees submitted frames; the | ||
| # step reports a skip elsewhere. | ||
| - name: Scene equivalence | ||
| shell: bash | ||
| run: | | ||
| python scripts/gpui-scene-digest.py \ | ||
| --base "$RUNNER_TEMP/bench/base" \ | ||
| --head "$RUNNER_TEMP/bench/head" \ | ||
| --out "$RUNNER_TEMP/bench/digests" \ | ||
| --summary "$GITHUB_STEP_SUMMARY" | ||
|
|
||
| - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 | ||
| if: always() | ||
| with: | ||
| name: gpui-bench-${{ matrix.settings.slug }} | ||
| path: | | ||
| ${{ runner.temp }}/bench/runs/*.md | ||
| ${{ runner.temp }}/bench/runs/*.json | ||
| ${{ runner.temp }}/bench/runs/*.log | ||
| if-no-files-found: ignore | ||
|
|
||
| test: | ||
| name: GPUI tests (${{ matrix.settings.name }}) | ||
| # Same as the benchmarks: GPUI's test builds are long, so forks do not start them. | ||
| if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository | ||
| timeout-minutes: 120 | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| settings: | ||
| - name: macOS | ||
| runner: blacksmith-6vcpu-macos-15 | ||
| - name: Windows | ||
| runner: blacksmith-8vcpu-windows-2025 | ||
| - name: Linux | ||
| runner: blacksmith-8vcpu-ubuntu-2404 | ||
| runs-on: ${{ matrix.settings.runner }} | ||
| env: | ||
| CARGO_PROFILE_DEV_DEBUG: "0" | ||
| CARGO_INCREMENTAL: "0" | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| persist-credentials: false | ||
|
|
||
| - name: Install Linux dependencies | ||
| if: ${{ runner.os == 'Linux' }} | ||
| run: | | ||
| sudo apt-get update | ||
| sudo apt-get install -y --no-install-recommends build-essential pkg-config clang libclang-dev \ | ||
| libx11-dev libx11-xcb-dev libxcb1-dev libxkbcommon-dev libxkbcommon-x11-dev libwayland-dev \ | ||
| libfontconfig1-dev libfreetype-dev libvulkan1 mesa-vulkan-drivers libegl1 libegl-mesa0 \ | ||
| libgl1-mesa-dri libssl-dev libasound2-dev libzstd-dev fonts-dejavu-core | ||
|
|
||
| - name: Allow long paths in Zed's git dependencies | ||
| if: ${{ runner.os == 'Windows' }} | ||
| run: git config --global core.longpaths true | ||
|
|
||
| - name: Rust setup | ||
| uses: dtolnay/rust-toolchain@1.95.0 | ||
|
|
||
| - uses: actions/setup-python@v5 | ||
| with: | ||
| python-version: "3.12" | ||
|
|
||
| - name: Prepare GPUI dependency | ||
| shell: bash | ||
| run: | | ||
| ./scripts/prepare-gpui-dependency.sh | ||
| echo "ZED_LOCK_HASH=$(python -c 'import hashlib,sys; print(hashlib.sha256(open(sys.argv[1],"rb").read()).hexdigest()[:16])' ../zed-cap/Cargo.lock)" >> "$GITHUB_ENV" | ||
|
|
||
| - name: Cargo cache | ||
| uses: actions/cache@v4 | ||
| with: | ||
| path: | | ||
| ~/.cargo/registry/index/ | ||
| ~/.cargo/registry/cache/ | ||
| ~/.cargo/git/db/ | ||
| ${{ github.workspace }}/../zed-cap/target | ||
| key: gpui-test-v1-${{ runner.os }}-${{ env.ZED_LOCK_HASH }}-${{ github.run_id }} | ||
| restore-keys: | | ||
| gpui-test-v1-${{ runner.os }}-${{ env.ZED_LOCK_HASH }}- | ||
| gpui-test-v1-${{ runner.os }}- | ||
|
|
||
| - name: gpui tests | ||
| shell: bash | ||
| working-directory: ../zed-cap | ||
| run: cargo +1.95.0 test -p gpui --features test-support --lib --tests --no-fail-fast | ||
|
|
||
| - name: Platform crate tests | ||
| shell: bash | ||
| working-directory: ../zed-cap | ||
| env: | ||
| GPUI_REQUIRE_TEST_DEVICE: "1" | ||
| run: | | ||
| set -euo pipefail | ||
| if [[ "$RUNNER_OS" == "Linux" ]]; then | ||
| WGPU_BACKEND=vulkan cargo +1.95.0 test -p gpui_wgpu --lib --no-fail-fast | ||
| elif [[ "$RUNNER_OS" == "macOS" ]]; then | ||
| cargo +1.95.0 test -p gpui_macos --lib --no-fail-fast | ||
| else | ||
| cargo +1.95.0 test -p gpui_windows --lib --no-fail-fast | ||
| fi |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: Untrusted fork PRs can repeatedly queue multi-hour GPUI benchmark jobs by editing benchmark-only paths
New PR workflows trigger 120-150 min macOS/Windows/Linux benchmark jobs when only the workflow, its scripts, or gpui-benches crate change.
Gate self-file/benchmark-triggered jobs to internal PRs or a maintainer-approved label/environment; keep fork runs bounded.
AI prompt
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in e3a8c7f: the
benchandtestjobs ingpui-bench.ymland theeditorjob ingpui-app-bench.ymlnow run only whengithub.event.pull_request.head.repo.full_name == github.repository(or on push/workflow_dispatch). Fork PRs no longer start them; maintainers can still measure a fork's patch withworkflow_dispatch.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correctly fixed: gating the long-running
bench,test, andeditorjobs to internal PRs viagithub.event.pull_request.head.repo.full_name == github.repository(andpush/workflow_dispatch) prevents untrusted forks from automatically queuing expensive benchmark runs, while preserving maintainer flexibility. Dismissing.