Skip to content

Commit c8ef5f9

Browse files
authored
Improve benchmark script (#1758)
1 parent e6344fc commit c8ef5f9

3 files changed

Lines changed: 75 additions & 43 deletions

File tree

.github/scripts/hyperfine-run-benchmarks.sh

Lines changed: 54 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ OUT_DIR=$(dirname "$COMMENT")
1010
META_WORKSPACE="${TARGET_WORKSPACE}-meta"
1111

1212
failed=false
13+
section_open=false
1314

1415
mkdir -p "$OUT_DIR"
1516
OUT_MD="$OUT_DIR/out.md"
@@ -31,10 +32,23 @@ write_section() {
3132
local title="$1"
3233
local description="${2:-}"
3334

35+
close_section
36+
write_blank_line
37+
write_line "<details>"
38+
write_line "<summary>$title</summary>"
3439
write_blank_line
35-
write_line "## $title"
3640
if [ -n "$description" ]; then
3741
write_line "$description"
42+
write_blank_line
43+
fi
44+
section_open=true
45+
}
46+
47+
close_section() {
48+
if [ "$section_open" = true ]; then
49+
write_blank_line
50+
write_line "</details>"
51+
section_open=false
3852
fi
3953
}
4054

@@ -66,24 +80,20 @@ check_variance() {
6680
fi
6781
}
6882

69-
write_benchmark_details() {
70-
write_line "<details>"
71-
write_line "<summary>Benchmark details</summary>"
72-
write_blank_line
73-
cat "$OUT_MD" >> "$COMMENT"
74-
write_blank_line
75-
write_line "</details>"
76-
}
77-
7883
benchmark() {
79-
local label="$1"
80-
local cmd="$2"
81-
local warmup="${3:-3}"
82-
local runs="${4:-30}"
83-
local setup="${5:-}"
84-
local prepare="${6:-}"
85-
local check_change="${7:-false}"
86-
local -a hyperfine_args=(-i -N -w "$warmup" -r "$runs" --export-markdown "$OUT_MD" --export-json "$OUT_JSON" --show-output)
84+
local cmd="$1"
85+
local warmup="${2:-3}"
86+
local runs="${3:-30}"
87+
local setup="${4:-}"
88+
local prepare="${5:-}"
89+
local check_change="${6:-false}"
90+
local label_suffix="${7:-}"
91+
local label="prek $cmd"
92+
local -a hyperfine_args=(-i -N -w "$warmup" -r "$runs" --export-markdown "$OUT_MD" --export-json "$OUT_JSON")
93+
94+
if [ -n "$label_suffix" ]; then
95+
label="$label $label_suffix"
96+
fi
8797

8898
if [ -n "$setup" ]; then
8999
hyperfine_args+=(--setup "$setup")
@@ -93,12 +103,14 @@ benchmark() {
93103
hyperfine_args+=(--prepare "$prepare")
94104
fi
95105

106+
write_blank_line
96107
write_line "### \`$label\`"
97108
if ! hyperfine "${hyperfine_args[@]}" --reference "$BASE_BINARY $cmd" "$HEAD_BINARY $cmd"; then
98109
write_line "⚠️ Benchmark failed for: $cmd"
99110
return 1
100111
fi
101-
write_benchmark_details
112+
cat "$OUT_MD" >> "$COMMENT"
113+
write_blank_line
102114
if [ "$check_change" = "true" ]; then
103115
check_variance "$cmd"
104116
fi
@@ -129,20 +141,26 @@ EOF
129141

130142
git add -A
131143
git commit -m "Meta hooks test" || { echo "Failed to commit meta hooks test"; exit 1; }
132-
prek install-hooks
144+
$HEAD_BINARY install-hooks
133145
}
134146

135147
# Add environment metadata
136-
write_line "## Hyperfine Performance"
148+
write_line "## ⚡️ Hyperfine Performance"
149+
write_blank_line
150+
write_line "<details>"
151+
write_line "<summary>Environment</summary>"
137152
write_blank_line
138-
write_line "**Environment:**"
139153
write_line "- OS: $(uname -s) $(uname -r)"
140154
write_line "- CPU: $(nproc) cores"
141155
write_line "- prek version: $CURRENT_PREK_VERSION"
142156
write_line "- Rust version: $(rustc --version)"
143157
write_line "- Hyperfine version: $(hyperfine --version)"
158+
write_blank_line
159+
write_line "</details>"
144160

145161
# Benchmark in the main repo
162+
write_section "CLI Commands" "Benchmarking basic commands in the main repo:"
163+
146164
CMDS=(
147165
"--version"
148166
"list"
@@ -157,9 +175,9 @@ for cmd in "${CMDS[@]}"; do
157175
fi
158176

159177
if [[ "$cmd" == "--version" ]] || [[ "$cmd" == "list" ]]; then
160-
benchmark "prek $cmd" "$cmd" 5 100
178+
benchmark "$cmd" 5 100
161179
else
162-
benchmark "prek $cmd" "$cmd" 3 50
180+
benchmark "$cmd" 3 50
163181
fi
164182
check_variance "$cmd"
165183
done
@@ -169,12 +187,12 @@ cd "$TARGET_WORKSPACE"
169187

170188
# Cold vs warm benchmarks before polluting cache
171189
write_section "Cold vs Warm Runs" "Comparing first run (cold) vs subsequent runs (warm cache):"
172-
benchmark "prek run --all-files (cold - no cache)" "run --all-files" 0 10 "rm -rf ~/.cache/prek" "git checkout -- ."
173-
benchmark "prek run --all-files (warm - with cache)" "run --all-files" 3 20 "" "git checkout -- ."
190+
benchmark "run --all-files" 0 10 "rm -rf ~/.cache/prek" "git checkout -- ." false "(cold - no cache)"
191+
benchmark "run --all-files" 3 20 "" "git checkout -- ." false "(warm - with cache)"
174192

175193
# Full benchmark suite with cache warmed up
176194
write_section "Full Hook Suite" "Running the builtin hook suite on the benchmark workspace:"
177-
benchmark "prek run --all-files (full builtin hook suite)" "run --all-files" 3 50 "" "git checkout -- ." true
195+
benchmark "run --all-files" 3 50 "" "git checkout -- ." true "(full builtin hook suite)"
178196

179197
# Individual hook performance
180198
write_section "Individual Hook Performance" "Benchmarking each hook individually on the test repo:"
@@ -189,24 +207,24 @@ INDIVIDUAL_HOOKS=(
189207
)
190208

191209
for hook in "${INDIVIDUAL_HOOKS[@]}"; do
192-
benchmark "prek run $hook --all-files" "run $hook --all-files" 3 30 "" "git checkout -- ."
210+
benchmark "run $hook --all-files" 3 30 "" "git checkout -- ."
193211
done
194212

195213
# Installation performance
196214
write_section "Installation Performance" "Benchmarking hook installation (fast path hooks skip Python setup):"
197-
benchmark "prek install-hooks (cold - no cache)" "install-hooks" 1 5 "rm -rf ~/.cache/prek/hooks ~/.cache/prek/repos"
198-
benchmark "prek install-hooks (warm - with cache)" "install-hooks" 1 5
215+
benchmark "install-hooks" 1 5 "rm -rf ~/.cache/prek/hooks ~/.cache/prek/repos" "" false "(cold - no cache)"
216+
benchmark "install-hooks" 1 5 "" "" false "(warm - with cache)"
199217

200218
# File filtering/scoping performance
201219
write_section "File Filtering/Scoping Performance" "Testing different file selection modes:"
202220

203221
git add -A
204-
benchmark "prek run (staged files only)" "run" 3 20 "" "sh -c 'git checkout -- . && git add -A'"
205-
benchmark "prek run --files '*.json' (specific file type)" "run --files '*.json'" 3 20
222+
benchmark "run" 3 20 "" "sh -c 'git checkout -- . && git add -A'" false "(staged files only)"
223+
benchmark "run --files '*.json'" 3 20 "" "" false "(specific file type)"
206224

207225
# Workspace discovery & initialization
208226
write_section "Workspace Discovery & Initialization" "Benchmarking hook discovery and initialization overhead:"
209-
benchmark "prek run --dry-run --all-files (measures init overhead)" "run --dry-run --all-files" 3 20
227+
benchmark "run --dry-run --all-files" 3 20 "" "" false "(measures init overhead)"
210228

211229
# Meta hooks performance
212230
write_section "Meta Hooks Performance" "Benchmarking meta hooks separately:"
@@ -219,9 +237,11 @@ META_HOOKS=(
219237
)
220238

221239
for hook in "${META_HOOKS[@]}"; do
222-
benchmark "prek run $hook --all-files" "run $hook --all-files" 3 15 "" "git checkout -- ."
240+
benchmark "run $hook --all-files" 3 15 "" "git checkout -- ."
223241
done
224242

243+
close_section
244+
225245
if [ "$failed" = true ]; then
226246
exit 1
227247
fi

.github/workflows/ci.yml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ jobs:
3939
outputs:
4040
test-code: ${{ !contains(github.event.pull_request.labels.*.name, 'test:skip') && (steps.changed.outputs.any_code_changed == 'true' || github.ref == 'refs/heads/master') }}
4141
save-rust-cache: ${{ github.ref == 'refs/heads/master' || steps.changed.outputs.cache_changed == 'true' }}
42-
# Run benchmarks only if Rust code changed
43-
run-bench: ${{ !contains(github.event.pull_request.labels.*.name, 'test:skip') && (steps.changed.outputs.rust_code_changed == 'true' || github.ref == 'refs/heads/master') }}
42+
# Run benchmarks if Rust code or benchmark-related files changed
43+
run-bench: ${{ !contains(github.event.pull_request.labels.*.name, 'test:skip') && (steps.changed.outputs.rust_code_changed == 'true' || steps.changed.outputs.bench_related_changed == 'true' || github.ref == 'refs/heads/master') }}
4444
steps:
4545
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
4646
with:
@@ -56,6 +56,7 @@ jobs:
5656
ANY_CODE_CHANGED=false
5757
CACHE_CHANGED=false
5858
RUST_CODE_CHANGED=false
59+
BENCH_RELATED_CHANGED=false
5960
6061
while IFS= read -r file; do
6162
# Check if cache-relevant files changed (Cargo files, toolchain, workflows)
@@ -70,6 +71,11 @@ jobs:
7071
RUST_CODE_CHANGED=true
7172
fi
7273
74+
if [[ "${file}" == ".github/workflows/performance.yml" ]] || [[ "${file}" =~ ^\.github/scripts/hyperfine-.*\.sh$ ]]; then
75+
echo "Detected benchmark-related change: ${file}"
76+
BENCH_RELATED_CHANGED=true
77+
fi
78+
7379
if [[ "${file}" =~ ^docs/ ]]; then
7480
echo "Skipping ${file} (matches docs/ pattern)"
7581
continue
@@ -90,6 +96,7 @@ jobs:
9096
echo "any_code_changed=${ANY_CODE_CHANGED}" >> "${GITHUB_OUTPUT}"
9197
echo "cache_changed=${CACHE_CHANGED}" >> "${GITHUB_OUTPUT}"
9298
echo "rust_code_changed=${RUST_CODE_CHANGED}" >> "${GITHUB_OUTPUT}"
99+
echo "bench_related_changed=${BENCH_RELATED_CHANGED}" >> "${GITHUB_OUTPUT}"
93100
94101
lint:
95102
name: "lint"

.github/workflows/performance.yml

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -110,41 +110,46 @@ jobs:
110110
hyperfine-benchmark:
111111
runs-on: ubuntu-latest
112112
name: "hyperfine benchmark"
113-
timeout-minutes: 30
113+
timeout-minutes: 10
114114
env:
115115
HYPERFINE_BENCHMARK_WORKSPACE: /tmp/prek-bench
116+
HYPERFINE_BIN_DIR: ${{ github.workspace }}/.hyperfine-bin
116117
HYPERFINE_RESULTS_FILE: ${{ github.workspace }}/hyperfine-benchmark.md
117-
HYPERFINE_HEAD_BINARY: ${{ github.workspace }}/target/profiling/prek
118-
HYPERFINE_BASE_BINARY: ${{ github.workspace }}/../bin/prek-${{ github.event.pull_request.base.sha }}
118+
HYPERFINE_HEAD_BINARY: prek-head
119+
HYPERFINE_BASE_BINARY: prek-base
119120
steps:
120121
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
121122
with:
122123
fetch-depth: 0
123124
persist-credentials: false
124125

126+
- name: Add hyperfine bin dir to PATH
127+
run: |
128+
mkdir -p "$HYPERFINE_BIN_DIR"
129+
echo "$HYPERFINE_BIN_DIR" >> "$GITHUB_PATH"
130+
125131
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
126132
with:
127133
save-if: ${{ inputs.save-rust-cache == 'true' }}
128134

129135
- id: base-binary-cache
130136
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
131137
with:
132-
path: ${{ env.HYPERFINE_BASE_BINARY }}
138+
path: ${{ env.HYPERFINE_BIN_DIR }}/${{ env.HYPERFINE_BASE_BINARY }}
133139
key: prek-hyperfine-base-${{ github.event.pull_request.base.sha }}-${{ hashFiles('Cargo.lock') }}-${{ runner.os }}-${{ runner.arch }}
134140

135141
- name: Build base version
136142
if: ${{ steps.base-binary-cache.outputs.cache-hit != 'true' }}
137143
env:
138144
BASE_VERSION: ${{ github.event.pull_request.base.sha }}
139145
run: |
140-
mkdir -p "$(dirname "$HYPERFINE_BASE_BINARY")"
141146
git checkout ${{ github.event.pull_request.base.sha }}
142-
cargo build --profile profiling && mv target/profiling/prek "$HYPERFINE_BASE_BINARY"
147+
cargo build --profile profiling && mv target/profiling/prek "$HYPERFINE_BIN_DIR/$HYPERFINE_BASE_BINARY"
143148
git checkout ${{ github.sha }}
144149
145150
- name: Build head version
146151
run: |
147-
cargo build --profile profiling
152+
cargo build --profile profiling && mv target/profiling/prek "$HYPERFINE_BIN_DIR/$HYPERFINE_HEAD_BINARY"
148153
149154
- name: Install hyperfine
150155
uses: taiki-e/install-action@f92912fad184299a31e22ad070a5059fd07d4f59 # v2.68.7

0 commit comments

Comments
 (0)