diff --git a/.github/scripts/allocator_bench_alpine.sh b/.github/scripts/allocator_bench_alpine.sh new file mode 100644 index 0000000..cb6b3db --- /dev/null +++ b/.github/scripts/allocator_bench_alpine.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +set -euo pipefail + +TOKIO_FEATURE="" +if [[ "${TOKIO_MULTI_THREAD:-false}" == "true" ]]; then + TOKIO_FEATURE="tokio-multi-thread" +fi + +docker run --rm \ + -v "$PWD:/work" \ + -w /work \ + -e TOKIO_FEATURE="$TOKIO_FEATURE" \ + -e PLATFORM_LABEL="${PLATFORM_LABEL:-unknown}" \ + rust:nightly-alpine sh -lc ' + set -eu + apk add --no-cache build-base pkgconfig time + + build_features() { + allocator="$1" + features="" + if [ "$allocator" != "system" ]; then + features="$allocator" + fi + if [ -n "$TOKIO_FEATURE" ]; then + if [ -n "$features" ]; then + features="$features,$TOKIO_FEATURE" + else + features="$TOKIO_FEATURE" + fi + fi + echo "$features" + } + + : > /work/alpine-results.tsv + for allocator in system jemalloc mimalloc_v2 mimalloc_v3; do + features="$(build_features "$allocator")" + if [ -n "$features" ]; then + output="$(/usr/bin/time -v cargo run --release --locked --features "$features" 2>&1 >/dev/null)" + else + output="$(/usr/bin/time -v cargo run --release --locked 2>&1 >/dev/null)" + fi + peak="$(echo "$output" | awk -F': ' '/Maximum resident set size/ {print $2; exit}')" + if [ -z "$peak" ]; then + echo "Failed to parse peak memory for $allocator" >&2 + exit 1 + fi + printf "%s\t%s\n" "$allocator" "$peak" >> /work/alpine-results.tsv + done + ' + +{ + echo "### ${PLATFORM_LABEL:-unknown} (tokio-multi-thread=${TOKIO_MULTI_THREAD:-false})" + echo "" + echo "| Allocator | Peak KB |" + echo "| --- | ---: |" + sort -n -k2,2 alpine-results.tsv | while IFS=$'\t' read -r allocator peak; do + echo "| $allocator | $peak |" + done + best="$(sort -n -k2,2 alpine-results.tsv | head -n1)" + best_allocator="${best%%$'\t'*}" + best_peak="${best#*$'\t'}" + echo "" + echo "**Best:** $best_allocator ($best_peak KB)" +} >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/scripts/allocator_bench_unix.sh b/.github/scripts/allocator_bench_unix.sh new file mode 100644 index 0000000..b91bc6c --- /dev/null +++ b/.github/scripts/allocator_bench_unix.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash +set -euo pipefail + +TOKIO_FEATURE="" +if [[ "${TOKIO_MULTI_THREAD:-false}" == "true" ]]; then + TOKIO_FEATURE="tokio-multi-thread" +fi + +allocators=(system jemalloc mimalloc_v2 mimalloc_v3) + +if [[ "$RUNNER_OS" == "Linux" ]]; then + time_cmd=(/usr/bin/time -v) + parse_peak() { + awk -F': ' '/Maximum resident set size/ {print $2; exit}' + } +else + time_cmd=(/usr/bin/time -l) + parse_peak() { + awk '/maximum resident set size/ {print $1; exit}' + } +fi + +build_features() { + local allocator="$1" + local features="" + if [[ "$allocator" != "system" ]]; then + features="$allocator" + fi + if [[ -n "$TOKIO_FEATURE" ]]; then + if [[ -n "$features" ]]; then + features="$features,$TOKIO_FEATURE" + else + features="$TOKIO_FEATURE" + fi + fi + echo "$features" +} + +run_one() { + local allocator="$1" + local features + features="$(build_features "$allocator")" + + local feature_args=() + if [[ -n "$features" ]]; then + feature_args=(--features "$features") + fi + + local output + output="$(${time_cmd[@]} cargo run --release --locked "${feature_args[@]}" 2>&1 >/dev/null)" + + local peak + peak="$(echo "$output" | parse_peak)" + if [[ -z "$peak" ]]; then + echo "Failed to parse peak memory for $allocator" >&2 + exit 1 + fi + + if [[ "$RUNNER_OS" != "Linux" ]]; then + peak=$((peak / 1024)) + fi + + printf "%s\t%s\n" "$allocator" "$peak" >> results.tsv +} + +if [[ "$RUNNER_OS" == "Windows" ]]; then + allocators=(system mimalloc_v2 mimalloc_v3) +fi + +: > results.tsv +for allocator in "${allocators[@]}"; do + run_one "$allocator" +done + +{ + echo "### ${PLATFORM_LABEL:-unknown} (tokio-multi-thread=${TOKIO_MULTI_THREAD:-false})" + echo "" + echo "| Allocator | Peak KB |" + echo "| --- | ---: |" + sort -n -k2,2 results.tsv | while IFS=$'\t' read -r allocator peak; do + echo "| $allocator | $peak |" + done + best="$(sort -n -k2,2 results.tsv | head -n1)" + best_allocator="${best%%$'\t'*}" + best_peak="${best#*$'\t'}" + echo "" + echo "**Best:** $best_allocator ($best_peak KB)" +} >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/scripts/allocator_bench_windows.ps1 b/.github/scripts/allocator_bench_windows.ps1 new file mode 100644 index 0000000..1939058 --- /dev/null +++ b/.github/scripts/allocator_bench_windows.ps1 @@ -0,0 +1,57 @@ +$tokioOn = $env:TOKIO_MULTI_THREAD +$tokioFeature = if ($tokioOn -eq "true") { "tokio-multi-thread" } else { "" } + +$allocators = @("system", "mimalloc_v2", "mimalloc_v3") + +function Build-Features([string]$allocator, [string]$tokio) { + $features = @() + if ($allocator -ne "system") { $features += $allocator } + if ($tokio) { $features += $tokio } + return ($features -join ",") +} + +function Run-One([string]$allocator, [string]$features) { + $args = @("run", "--release", "--locked") + if ($features) { $args += "--features"; $args += $features } + + $peak = 0 + $proc = Start-Process -FilePath "cargo" -ArgumentList $args -PassThru -NoNewWindow + while (-not $proc.HasExited) { + Start-Sleep -Milliseconds 200 + try { + $p = Get-Process -Id $proc.Id -ErrorAction Stop + $current = [math]::Max($p.WorkingSet64, $p.PeakWorkingSet64) + if ($current -gt $peak) { $peak = $current } + } catch { } + } + $peakKb = [math]::Floor($peak / 1kb) + Add-Content -Path results.tsv -Value "$allocator`t$peakKb" +} + +if (Test-Path results.tsv) { Remove-Item results.tsv -Force } +foreach ($allocator in $allocators) { + $features = Build-Features $allocator $tokioFeature + Run-One $allocator $features +} + +$rows = Get-Content results.tsv | ForEach-Object { + $parts = $_ -split "`t" + [pscustomobject]@{ + Allocator = $parts[0] + PeakKB = [int]$parts[1] + } +} +$sorted = $rows | Sort-Object PeakKB +$best = $sorted | Select-Object -First 1 + +$summary = @() +$summary += "### $($env:PLATFORM_LABEL) (tokio-multi-thread=$tokioOn)" +$summary += "" +$summary += "| Allocator | Peak KB |" +$summary += "| --- | ---: |" +foreach ($row in $sorted) { + $summary += "| $($row.Allocator) | $($row.PeakKB) |" +} +$summary += "" +$summary += "**Best:** $($best.Allocator) ($($best.PeakKB) KB)" +$summary -join "`n" | Add-Content $env:GITHUB_STEP_SUMMARY diff --git a/.github/workflows/allocator-bench.yml b/.github/workflows/allocator-bench.yml new file mode 100644 index 0000000..7048794 --- /dev/null +++ b/.github/workflows/allocator-bench.yml @@ -0,0 +1,73 @@ +name: Allocator Bench +on: + workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.run_id }} + cancel-in-progress: true +permissions: + contents: read +jobs: + bench: + name: ${{ matrix.name }} (tokio-multi-thread=${{ matrix.tokio_multi_thread }}) + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + tokio_multi_thread: [true, false] + include: + - name: ubuntu-24.04-arm-1 + platform: ubuntu-24.04-arm + runner: ubuntu-24.04-arm + alpine: false + - name: ubuntu-24.04-arm-2 + platform: ubuntu-24.04-arm + runner: ubuntu-24.04-arm + alpine: false + - name: windows-2025 + platform: windows-2025 + runner: windows-2025 + alpine: false + - name: windows-11-arm + platform: windows-11-arm + runner: windows-11-arm + alpine: false + - name: macos-15-x86_64 + platform: macos-15 x86_64 + runner: macos-15-large + alpine: false + - name: macos-15-arm64 + platform: macos-15 arm64 + runner: macos-15 + alpine: false + - name: alpine-dockerized + platform: alpine (dockerized) + runner: ubuntu-24.04 + alpine: true + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + - if: ${{ !matrix.alpine }} + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: beta + cache: true + cache-key: allocator-bench-${{ matrix.runner }} + - if: ${{ !matrix.alpine && runner.os != 'Windows' }} + shell: bash + env: + TOKIO_MULTI_THREAD: ${{ matrix.tokio_multi_thread }} + PLATFORM_LABEL: ${{ matrix.platform }} + run: bash .github/scripts/allocator_bench_unix.sh + - if: ${{ !matrix.alpine && runner.os == 'Windows' }} + shell: pwsh + env: + TOKIO_MULTI_THREAD: ${{ matrix.tokio_multi_thread }} + PLATFORM_LABEL: ${{ matrix.platform }} + run: ./.github/scripts/allocator_bench_windows.ps1 + - if: ${{ matrix.alpine }} + shell: bash + env: + TOKIO_MULTI_THREAD: ${{ matrix.tokio_multi_thread }} + PLATFORM_LABEL: ${{ matrix.platform }} + run: bash .github/scripts/allocator_bench_alpine.sh \ No newline at end of file