1+ #! /bin/bash
2+
3+ # CPU scaling benchmark that runs benchmarks locally
4+ # This script runs a command multiple times with different HARDWARE_CONCURRENCY values
5+ # and tracks the scaling performance of specific BB_BENCH entries
6+ # Uses --bench_out flag to get JSON output for accurate timing extraction
7+
8+ set -e
9+
10+ # Colors for output
11+ RED=' \033[0;31m'
12+ GREEN=' \033[0;32m'
13+ YELLOW=' \033[1;33m'
14+ BLUE=' \033[0;34m'
15+ CYAN=' \033[0;36m'
16+ MAGENTA=' \033[0;35m'
17+ NC=' \033[0m' # No Color
18+
19+ # Parse arguments
20+ if [ $# -lt 2 ]; then
21+ echo -e " ${RED} Usage: $0 \" benchmark_name\" \" command\" [cpu_counts]${NC} "
22+ echo -e " Example: $0 \" ClientIvcProve\" \" ./build/bin/bb prove --ivc_inputs_path input.msgpack --scheme client_ivc\" "
23+ echo -e " Example: $0 \" construct_mock_function_circuit\" \" ./build/bin/ultra_honk_bench --benchmark_filter=.*power_of_2.*/15\" \" 1,2,4,8\" "
24+ exit 1
25+ fi
26+
27+ BENCH_NAME=" $1 "
28+ COMMAND=" $2 "
29+ CPU_LIST=" ${3:- 1,2,4,8,16} "
30+
31+ # Convert comma-separated list to array
32+ IFS=' ,' read -ra CPU_COUNTS <<< " $CPU_LIST"
33+
34+ # Create output directory with timestamp
35+ TIMESTAMP=$( date +%Y%m%d_%H%M%S)
36+ OUTPUT_DIR=" bench_scaling_local_${TIMESTAMP} "
37+ mkdir -p " $OUTPUT_DIR "
38+
39+ # Results file
40+ RESULTS_FILE=" $OUTPUT_DIR /scaling_results.txt"
41+ CSV_FILE=" $OUTPUT_DIR /scaling_results.csv"
42+
43+ echo -e " ${GREEN} ╔════════════════════════════════════════════════════════════════╗${NC} "
44+ echo -e " ${GREEN} ║ CPU Scaling Benchmark (Local Execution) ║${NC} "
45+ echo -e " ${GREEN} ╚════════════════════════════════════════════════════════════════╝${NC} "
46+ echo " "
47+ echo -e " ${CYAN} Benchmark Entry:${NC} ${YELLOW} $BENCH_NAME ${NC} "
48+ echo -e " ${CYAN} Command:${NC} $COMMAND "
49+ echo -e " ${CYAN} CPU Counts:${NC} ${CPU_COUNTS[@]} "
50+ echo -e " ${CYAN} Machine:${NC} $( hostname) "
51+ echo -e " ${CYAN} Output Directory:${NC} $OUTPUT_DIR "
52+ echo " "
53+
54+ # Initialize results file
55+ echo " CPU Scaling Benchmark: $BENCH_NAME " > " $RESULTS_FILE "
56+ echo " Command: $COMMAND " >> " $RESULTS_FILE "
57+ echo " Machine: $( hostname) " >> " $RESULTS_FILE "
58+ echo " Date: $( date) " >> " $RESULTS_FILE "
59+ echo " ================================================" >> " $RESULTS_FILE "
60+ echo " " >> " $RESULTS_FILE "
61+
62+ # Initialize CSV file
63+ echo " CPUs,Time_ms,Time_s,Speedup,Efficiency" > " $CSV_FILE "
64+
65+ # Function to extract time for specific benchmark entry from JSON
66+ extract_bench_time () {
67+ local json_file=$1
68+ local bench_name=$2
69+
70+ # Extract time from JSON file using grep and sed
71+ # JSON format is: {"benchmark_name": time_in_nanoseconds, ...}
72+ local time_ns=" "
73+
74+ if [ -f " $json_file " ]; then
75+ # Extract the value for the specific benchmark name from JSON
76+ time_ns=$( grep -oP " \" ${bench_name// \\ / \\\\ } \" :\s*\K\d+" " $json_file " 2> /dev/null | head -1)
77+ fi
78+
79+ # If JSON extraction failed, try to extract from log file (fallback)
80+ if [ -z " $time_ns " ] && [ -f " ${json_file%/ bench.json} /output.log" ]; then
81+ local log_file=" ${json_file%/ bench.json} /output.log"
82+ # Try to extract from hierarchical BB_BENCH output
83+ # Look for pattern like: " ├─ ClientIvcProve ... 28.13s"
84+ local time_s=$( grep -E " ├─.*${bench_name} " " $log_file " | grep -oP ' \d+\.\d+s' | grep -oP ' \d+\.\d+' | head -1)
85+ if [ -n " $time_s " ]; then
86+ # Convert seconds to nanoseconds
87+ time_ns=$( awk -v s=" $time_s " ' BEGIN{printf "%.0f", s * 1000000000}' )
88+ fi
89+ fi
90+
91+ echo " $time_ns "
92+ }
93+
94+ # Store baseline time for speedup calculation
95+ BASELINE_TIME=" "
96+
97+ # Arrays to store results
98+ declare -a ALL_CPUS=()
99+ declare -a ALL_TIMES=()
100+ declare -a ALL_SPEEDUPS=()
101+
102+ echo -e " ${BLUE} Starting benchmark runs locally...${NC} "
103+ echo " "
104+
105+ # Run benchmark for each CPU count
106+ for cpu_count in " ${CPU_COUNTS[@]} " ; do
107+ echo -e " ${YELLOW} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC} "
108+ echo -e " ${YELLOW} Running with ${cpu_count} CPU(s)...${NC} "
109+
110+ # Create output subdirectory
111+ run_dir=" $OUTPUT_DIR /run_${cpu_count} cpus"
112+ mkdir -p " $run_dir "
113+ log_file=" $run_dir /output.log"
114+ bench_json_file=" $run_dir /bench.json"
115+
116+ # Run command locally with specified CPU count
117+ echo -e " ${CYAN} Executing locally...${NC} "
118+ start_time=$( date +%s.%N)
119+
120+ # Clean up any stale benchmark file
121+ rm -f " $bench_json_file "
122+
123+ # Execute the command locally with HARDWARE_CONCURRENCY environment variable
124+ # Add --bench_out flag to get JSON output
125+ HARDWARE_CONCURRENCY=$cpu_count eval " $COMMAND --bench_out $bench_json_file " 2>&1 | tee " $log_file "
126+
127+ end_time=$( date +%s.%N)
128+ wall_time=$( awk -v e=" $end_time " -v s=" $start_time " ' BEGIN{printf "%.2f", e-s}' )
129+
130+ # Extract the specific benchmark time from JSON file
131+ bench_time_ns=$( extract_bench_time " $bench_json_file " " $BENCH_NAME " )
132+
133+ if [ -z " $bench_time_ns " ] || [ " $bench_time_ns " = " 0" ]; then
134+ echo -e " ${RED} Warning: Could not extract timing for '$BENCH_NAME ' from JSON${NC} "
135+ echo -e " ${YELLOW} Check the JSON file: $bench_json_file ${NC} "
136+
137+ # Show what's in the JSON file for debugging
138+ if [ -f " $bench_json_file " ]; then
139+ echo -e " ${YELLOW} JSON content (first 500 chars):${NC} "
140+ head -c 500 " $bench_json_file "
141+ echo " "
142+ fi
143+
144+ echo " CPUs: $cpu_count - No timing data found" >> " $RESULTS_FILE "
145+ continue
146+ fi
147+
148+ # Convert to milliseconds and seconds
149+ bench_time_ms=$( awk -v ns=" $bench_time_ns " ' BEGIN{printf "%.2f", ns / 1000000}' )
150+ bench_time_s=$( awk -v ns=" $bench_time_ns " ' BEGIN{printf "%.3f", ns / 1000000000}' )
151+
152+ # Calculate speedup and efficiency
153+ if [ -z " $BASELINE_TIME " ]; then
154+ BASELINE_TIME=" $bench_time_ns "
155+ speedup=" 1.00"
156+ efficiency=" 100.0"
157+ else
158+ speedup=$( awk -v base=" $BASELINE_TIME " -v curr=" $bench_time_ns " ' BEGIN{printf "%.2f", base / curr}' )
159+ efficiency=$( awk -v sp=" $speedup " -v cpus=" $cpu_count " ' BEGIN{printf "%.1f", (sp / cpus) * 100}' )
160+ fi
161+
162+ # Store results
163+ ALL_CPUS+=(" $cpu_count " )
164+ ALL_TIMES+=(" $bench_time_ms " )
165+ ALL_SPEEDUPS+=(" $speedup " )
166+
167+ # Write to results file
168+ echo " CPUs: $cpu_count " >> " $RESULTS_FILE "
169+ echo " Time: ${bench_time_ms} ms (${bench_time_s} s)" >> " $RESULTS_FILE "
170+ echo " Speedup: ${speedup} x" >> " $RESULTS_FILE "
171+ echo " Efficiency: ${efficiency} %" >> " $RESULTS_FILE "
172+ echo " Wall time: ${wall_time} s" >> " $RESULTS_FILE "
173+ echo " " >> " $RESULTS_FILE "
174+
175+ # Write to CSV
176+ echo " $cpu_count ,$bench_time_ms ,$bench_time_s ,$speedup ,$efficiency " >> " $CSV_FILE "
177+
178+ # Display results
179+ echo -e " ${GREEN} ✓ Completed${NC} "
180+ echo -e " ${CYAN} Time for '$BENCH_NAME ':${NC} ${bench_time_ms} ms"
181+ echo -e " ${CYAN} Speedup:${NC} ${speedup} x"
182+ echo -e " ${CYAN} Efficiency:${NC} ${efficiency} %"
183+ echo " "
184+ done
185+
186+ # Generate summary
187+ echo -e " ${GREEN} ╔════════════════════════════════════════════════════════════════╗${NC} "
188+ echo -e " ${GREEN} ║ SUMMARY ║${NC} "
189+ echo -e " ${GREEN} ╚════════════════════════════════════════════════════════════════╝${NC} "
190+ echo " "
191+
192+ # Print table header
193+ printf " ${CYAN} %-8s %-15s %-12s %-12s${NC} \n" " CPUs" " Time (ms)" " Speedup" " Efficiency"
194+ printf " ${CYAN} %-8s %-15s %-12s %-12s${NC} \n" " ────" " ──────────" " ───────" " ──────────"
195+
196+ # Print results table
197+ for i in " ${! ALL_CPUS[@]} " ; do
198+ cpu=" ${ALL_CPUS[$i]} "
199+ time=" ${ALL_TIMES[$i]} "
200+ speedup=" ${ALL_SPEEDUPS[$i]} "
201+
202+ if [ " $i " -eq 0 ]; then
203+ efficiency=" 100.0%"
204+ else
205+ efficiency=$( awk -v sp=" $speedup " -v cpus=" $cpu " ' BEGIN{printf "%.1f%%", (sp / cpus) * 100}' )
206+ fi
207+
208+ # Color code based on efficiency
209+ if [ " $i " -eq 0 ]; then
210+ color=" ${GREEN} "
211+ else
212+ eff_val=$( echo " $efficiency " | sed ' s/%//' )
213+ if awk -v eff=" $eff_val " ' BEGIN {exit !(eff > 75)}' ; then
214+ color=" ${GREEN} "
215+ elif awk -v eff=" $eff_val " ' BEGIN {exit !(eff > 50)}' ; then
216+ color=" ${YELLOW} "
217+ else
218+ color=" ${RED} "
219+ fi
220+ fi
221+
222+ printf " ${color} %-8s %-15s %-12s %-12s${NC} \n" " $cpu " " $time " " ${speedup} x" " $efficiency "
223+ done
224+
225+ echo " "
226+ echo -e " ${MAGENTA} ═══════════════════════════════════════════════════════════════${NC} "
227+ echo " "
228+
229+ # Generate scaling plot (ASCII art)
230+ echo -e " ${CYAN} Scaling Visualization:${NC} "
231+ echo " "
232+
233+ if [ " ${# ALL_TIMES[@]} " -gt 0 ]; then
234+ # Find max time for scaling
235+ max_time=$( printf ' %s\n' " ${ALL_TIMES[@]} " | sort -rn | head -1)
236+
237+ # Create ASCII bar chart
238+ for i in " ${! ALL_CPUS[@]} " ; do
239+ cpu=" ${ALL_CPUS[$i]} "
240+ time=" ${ALL_TIMES[$i]} "
241+
242+ # Calculate bar length (max 50 chars)
243+ bar_len=$( awk -v t=" $time " -v m=" $max_time " ' BEGIN{printf "%.0f", (t/m) * 50}' )
244+
245+ # Create bar
246+ bar=" "
247+ for (( j= 0 ; j< bar_len; j++ )) ; do
248+ bar=" ${bar} █"
249+ done
250+
251+ printf " %-6s │%s %.2f ms\n" " ${cpu} CPU" " $bar " " $time "
252+ done
253+ fi
254+
255+ echo " "
256+ echo -e " ${GREEN} Results saved to:${NC} "
257+ echo " - Summary: $RESULTS_FILE "
258+ echo " - CSV: $CSV_FILE "
259+ echo " - Logs: $OUTPUT_DIR /run_*cpus/output.log"
260+ echo " "
261+
262+ # Check for scaling issues
263+ if [ " ${# ALL_SPEEDUPS[@]} " -gt 1 ]; then
264+ last_speedup=" ${ALL_SPEEDUPS[-1]} "
265+ last_cpu=" ${ALL_CPUS[-1]} "
266+ actual_efficiency=$( awk -v sp=" $last_speedup " -v cpus=" $last_cpu " ' BEGIN{printf "%.1f", (sp / cpus) * 100}' )
267+
268+ if awk -v eff=" $actual_efficiency " ' BEGIN {exit !(eff < 50)}' ; then
269+ echo -e " ${YELLOW} ⚠ Warning: Poor scaling detected!${NC} "
270+ echo -e " At ${last_cpu} CPUs: ${actual_efficiency} % efficiency"
271+ echo -e " Consider investigating thread contention or memory bottlenecks."
272+ elif awk -v eff=" $actual_efficiency " ' BEGIN {exit !(eff < 75)}' ; then
273+ echo -e " ${YELLOW} Note: Moderate scaling efficiency at high CPU counts.${NC} "
274+ echo -e " At ${last_cpu} CPUs: ${actual_efficiency} % efficiency"
275+ else
276+ echo -e " ${GREEN} ✓ Good scaling efficiency maintained!${NC} "
277+ echo -e " At ${last_cpu} CPUs: ${actual_efficiency} % efficiency"
278+ fi
279+ fi
280+
281+ echo " "
0 commit comments