perf(bench): switch codec benches to shared decoder + warmup pattern #19
Workflow file for this run
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
| name: PR checks | |
| # Per-package parallel pipeline: each codec is built, tested, and benchmarked | |
| # independently. The matrix mirrors what CircleCI was doing — every entry | |
| # runs concurrently, any failure fails the workflow. | |
| # | |
| # CodSpeed's first-class GHA integration replaces the manual codspeed-bench | |
| # job from CircleCI: `CodSpeedHQ/action@v3` installs valgrind, sets up | |
| # instrumentation, and uploads results in one step. | |
| on: | |
| pull_request: | |
| push: | |
| branches-ignore: | |
| - main | |
| # workflow_dispatch lets CodSpeed trigger a backtest run from the | |
| # dashboard (to seed initial perf data after the repo is connected). | |
| workflow_dispatch: | |
| # Cancel in-flight runs when a new push lands on the same PR / branch. | |
| concurrency: | |
| group: pr-checks-${{ github.workflow }}-${{ github.head_ref || github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| pull-requests: write # CodSpeed action posts a sticky PR comment | |
| id-token: write # OIDC token used by CodSpeedHQ/action for auth | |
| jobs: | |
| detect-changes: | |
| # Build the matrix dynamically from the set of packages that actually | |
| # changed since main. Mirrors CircleCI's --since main skip logic. On a | |
| # PR where every README was touched (this branch) the list is the full | |
| # 8 packages; on a docs-only PR the build matrix is empty. | |
| runs-on: ubuntu-latest | |
| outputs: | |
| packages: ${{ steps.list.outputs.packages }} | |
| any: ${{ steps.list.outputs.any }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - id: list | |
| name: List changed packages | |
| run: | | |
| set -e | |
| git fetch --no-tags --depth=50 origin main || true | |
| BASE=$(git merge-base origin/main HEAD || echo "origin/main") | |
| ALL=(charls libjpeg-turbo-8bit libjpeg-turbo-12bit openjpeg openjphjs little-endian big-endian dicom-codec) | |
| changed=() | |
| for pkg in "${ALL[@]}"; do | |
| if ! git diff --quiet "$BASE"..HEAD -- "packages/$pkg/"; then | |
| changed+=("$pkg") | |
| fi | |
| done | |
| if [ ${#changed[@]} -eq 0 ]; then | |
| echo "No packages changed since $BASE." | |
| echo 'packages=[]' >> "$GITHUB_OUTPUT" | |
| echo "any=false" >> "$GITHUB_OUTPUT" | |
| else | |
| json=$(printf '%s\n' "${changed[@]}" | jq -R . | jq -s -c .) | |
| echo "Changed packages: $json" | |
| echo "packages=$json" >> "$GITHUB_OUTPUT" | |
| echo "any=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| build: | |
| needs: detect-changes | |
| if: needs.detect-changes.outputs.any == 'true' | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| package: ${{ fromJson(needs.detect-changes.outputs.packages) }} | |
| runs-on: ubuntu-latest | |
| container: | |
| image: emscripten/emsdk:3.1.74 | |
| steps: | |
| - name: Install yarn + cmake + C++ build deps | |
| run: | | |
| npm install --global yarn@1.22.22 | |
| apt-get update | |
| apt-get -y install build-essential git | |
| wget -qO- "https://cmake.org/files/v3.17/cmake-3.17.4-Linux-x86_64.tar.gz" \ | |
| | tar --strip-components=1 -xz -C /usr/local | |
| apt-get autoremove -y | |
| apt-get clean -y | |
| rm -rf /var/lib/apt/lists/* | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Allow git to operate on the workspace | |
| # The container runs as root but the workspace is owned by the | |
| # checkout action's user, which makes git complain about | |
| # `dubious ownership`. Mark it safe. | |
| run: git config --global --add safe.directory "$GITHUB_WORKSPACE" | |
| - name: Init submodules for this package | |
| run: | | |
| if [ -d "packages/${{ matrix.package }}/extern" ]; then | |
| git submodule update --init --recursive "packages/${{ matrix.package }}/extern" | |
| else | |
| echo "No extern/ submodule for ${{ matrix.package }}; skipping." | |
| fi | |
| - name: Restore yarn cache | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cache/yarn | |
| key: yarn-${{ hashFiles('yarn.lock') }} | |
| restore-keys: yarn- | |
| - name: Install dependencies | |
| run: yarn install --frozen-lockfile | |
| - name: Build | |
| run: cd "packages/${{ matrix.package }}" && yarn run build:ci | |
| - name: Ensure dist exists (no-op packages still need a placeholder) | |
| run: mkdir -p "packages/${{ matrix.package }}/dist" | |
| - name: Upload dist | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: dist-${{ matrix.package }} | |
| path: packages/${{ matrix.package }}/dist | |
| if-no-files-found: ignore | |
| retention-days: 7 | |
| test: | |
| needs: [detect-changes, build] | |
| if: needs.detect-changes.outputs.any == 'true' | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| package: ${{ fromJson(needs.detect-changes.outputs.packages) }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: '22' | |
| - name: Download all built dists | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: dist-* | |
| path: tmp/ | |
| - name: Replay dists into packages/<pkg>/dist | |
| # actions/download-artifact lands each artifact in tmp/<name>/. | |
| # Move each into its proper packages/<pkg>/dist location so vitest | |
| # finds them, mirroring how CircleCI workspace persist worked. | |
| run: | | |
| set -e | |
| for d in tmp/dist-*; do | |
| [ -d "$d" ] || continue | |
| pkg=$(basename "$d" | sed 's/^dist-//') | |
| mkdir -p "packages/$pkg/dist" | |
| shopt -s dotglob nullglob | |
| cp -r "$d"/* "packages/$pkg/dist/" 2>/dev/null || true | |
| done | |
| ls packages/*/dist 2>/dev/null | head | |
| - name: Restore yarn cache | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cache/yarn | |
| key: yarn-${{ hashFiles('yarn.lock') }} | |
| restore-keys: yarn- | |
| - name: Install dependencies | |
| run: yarn install --frozen-lockfile | |
| - name: Test | |
| run: cd "packages/${{ matrix.package }}" && yarn run test:ci | |
| codspeed-bench: | |
| needs: [detect-changes, build] | |
| if: needs.detect-changes.outputs.any == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: '22' | |
| - name: Download all built dists | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: dist-* | |
| path: tmp/ | |
| - name: Replay dists into packages/<pkg>/dist | |
| run: | | |
| set -e | |
| for d in tmp/dist-*; do | |
| [ -d "$d" ] || continue | |
| pkg=$(basename "$d" | sed 's/^dist-//') | |
| mkdir -p "packages/$pkg/dist" | |
| shopt -s dotglob nullglob | |
| cp -r "$d"/* "packages/$pkg/dist/" 2>/dev/null || true | |
| done | |
| - name: Restore yarn cache | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cache/yarn | |
| key: yarn-${{ hashFiles('yarn.lock') }} | |
| restore-keys: yarn- | |
| - name: Install dependencies | |
| run: yarn install --frozen-lockfile | |
| - name: Run CodSpeed benchmarks | |
| # CodSpeedHQ/action@v4 sets up CPU simulation (deterministic, <1% | |
| # variance, hardware-independent), runs the inner command under | |
| # valgrind, uploads to codspeed.io, and posts/updates a sticky PR | |
| # comment with the per-bench deltas. Authenticates via GitHub OIDC | |
| # (id-token: write above) so no CODSPEED_TOKEN secret is needed. | |
| uses: CodSpeedHQ/action@v4 | |
| with: | |
| mode: simulation | |
| run: yarn run bench |