|
| 1 | +#!/bin/bash |
| 2 | + |
| 3 | +# CPU scaling benchmark wrapper that uses benchmark_remote.sh properly |
| 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 | +# Check if required environment variables are set for remote execution |
| 35 | +if [ -z "${BB_SSH_KEY:-}" ] || [ -z "${BB_SSH_INSTANCE:-}" ] || [ -z "${BB_SSH_CPP_PATH:-}" ]; then |
| 36 | + echo -e "${RED}Error: Remote execution requires BB_SSH_KEY, BB_SSH_INSTANCE, and BB_SSH_CPP_PATH environment variables${NC}" |
| 37 | + echo "Please set:" |
| 38 | + echo " export BB_SSH_KEY='-i /path/to/key.pem'" |
| 39 | + echo " export BB_SSH_INSTANCE='[email protected]'" |
| 40 | + echo " export BB_SSH_CPP_PATH='/path/to/barretenberg/cpp'" |
| 41 | + exit 1 |
| 42 | +fi |
| 43 | + |
| 44 | +# Create output directory with timestamp |
| 45 | +TIMESTAMP=$(date +%Y%m%d_%H%M%S) |
| 46 | +OUTPUT_DIR="bench_scaling_remote_${TIMESTAMP}" |
| 47 | +mkdir -p "$OUTPUT_DIR" |
| 48 | + |
| 49 | +# Results file |
| 50 | +RESULTS_FILE="$OUTPUT_DIR/scaling_results.txt" |
| 51 | +CSV_FILE="$OUTPUT_DIR/scaling_results.csv" |
| 52 | + |
| 53 | +echo -e "${GREEN}╔════════════════════════════════════════════════════════════════╗${NC}" |
| 54 | +echo -e "${GREEN}║ CPU Scaling Benchmark (Remote Execution) ║${NC}" |
| 55 | +echo -e "${GREEN}╚════════════════════════════════════════════════════════════════╝${NC}" |
| 56 | +echo "" |
| 57 | +echo -e "${CYAN}Benchmark Entry:${NC} ${YELLOW}$BENCH_NAME${NC}" |
| 58 | +echo -e "${CYAN}Command:${NC} $COMMAND" |
| 59 | +echo -e "${CYAN}CPU Counts:${NC} ${CPU_COUNTS[@]}" |
| 60 | +echo -e "${CYAN}Remote Host:${NC} ${BB_SSH_INSTANCE}" |
| 61 | +echo -e "${CYAN}Remote Path:${NC} ${BB_SSH_CPP_PATH}" |
| 62 | +echo -e "${CYAN}Output Directory:${NC} $OUTPUT_DIR" |
| 63 | +echo "" |
| 64 | + |
| 65 | +# Initialize results file |
| 66 | +echo "CPU Scaling Benchmark: $BENCH_NAME" > "$RESULTS_FILE" |
| 67 | +echo "Command: $COMMAND" >> "$RESULTS_FILE" |
| 68 | +echo "Remote Host: $BB_SSH_INSTANCE" >> "$RESULTS_FILE" |
| 69 | +echo "Date: $(date)" >> "$RESULTS_FILE" |
| 70 | +echo "================================================" >> "$RESULTS_FILE" |
| 71 | +echo "" >> "$RESULTS_FILE" |
| 72 | + |
| 73 | +# Initialize CSV file |
| 74 | +echo "CPUs,Time_ms,Time_s,Speedup,Efficiency" > "$CSV_FILE" |
| 75 | + |
| 76 | +# Function to extract time for specific benchmark entry from JSON |
| 77 | +extract_bench_time() { |
| 78 | + local json_file=$1 |
| 79 | + local bench_name=$2 |
| 80 | + |
| 81 | + # Extract time from JSON file using grep and sed |
| 82 | + # JSON format is: {"benchmark_name": time_in_nanoseconds, ...} |
| 83 | + local time_ns="" |
| 84 | + |
| 85 | + if [ -f "$json_file" ]; then |
| 86 | + # Extract the value for the specific benchmark name from JSON |
| 87 | + time_ns=$(grep -oP "\"${bench_name//\\/\\\\}\":\s*\K\d+" "$json_file" 2>/dev/null | head -1) |
| 88 | + fi |
| 89 | + |
| 90 | + # If JSON extraction failed, try to extract from log file (fallback) |
| 91 | + if [ -z "$time_ns" ] && [ -f "${json_file%/bench.json}/output.log" ]; then |
| 92 | + local log_file="${json_file%/bench.json}/output.log" |
| 93 | + # Try to extract from hierarchical BB_BENCH output |
| 94 | + # Look for pattern like: " ├─ ClientIvcProve ... 28.13s" |
| 95 | + local time_s=$(grep -E "├─.*${bench_name}" "$log_file" | grep -oP '\d+\.\d+s' | grep -oP '\d+\.\d+' | head -1) |
| 96 | + if [ -n "$time_s" ]; then |
| 97 | + # Convert seconds to nanoseconds |
| 98 | + time_ns=$(awk -v s="$time_s" 'BEGIN{printf "%.0f", s * 1000000000}') |
| 99 | + fi |
| 100 | + fi |
| 101 | + |
| 102 | + echo "$time_ns" |
| 103 | +} |
| 104 | + |
| 105 | +# Store baseline time for speedup calculation |
| 106 | +BASELINE_TIME="" |
| 107 | + |
| 108 | +# Arrays to store results |
| 109 | +declare -a ALL_CPUS=() |
| 110 | +declare -a ALL_TIMES=() |
| 111 | +declare -a ALL_SPEEDUPS=() |
| 112 | + |
| 113 | +echo -e "${BLUE}Starting benchmark runs on remote machine...${NC}" |
| 114 | +echo "" |
| 115 | + |
| 116 | +# Run benchmark for each CPU count |
| 117 | +for cpu_count in "${CPU_COUNTS[@]}"; do |
| 118 | + echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" |
| 119 | + echo -e "${YELLOW}Running with ${cpu_count} CPU(s)...${NC}" |
| 120 | + |
| 121 | + # Create output subdirectory |
| 122 | + run_dir="$OUTPUT_DIR/run_${cpu_count}cpus" |
| 123 | + mkdir -p "$run_dir" |
| 124 | + log_file="$run_dir/output.log" |
| 125 | + |
| 126 | + # Run command on remote machine with specified CPU count |
| 127 | + echo -e "${CYAN}Executing on remote via benchmark_remote.sh...${NC}" |
| 128 | + start_time=$(date +%s.%N) |
| 129 | + |
| 130 | + # Use benchmark_remote.sh to execute on remote with --bench_out for JSON output |
| 131 | + # The benchmark_remote.sh script handles locking and setup |
| 132 | + # Use tee to show output in real-time AND save to log file |
| 133 | + bench_json_file="$run_dir/bench.json" |
| 134 | + ./scripts/benchmark_remote.sh bb "HARDWARE_CONCURRENCY=$cpu_count $COMMAND --bench_out /tmp/bench_${cpu_count}.json" 2>&1 | tee "$log_file" |
| 135 | + |
| 136 | + # Retrieve the JSON file from remote |
| 137 | + ssh $BB_SSH_KEY $BB_SSH_INSTANCE "cat /tmp/bench_${cpu_count}.json" > "$bench_json_file" 2>/dev/null |
| 138 | + |
| 139 | + end_time=$(date +%s.%N) |
| 140 | + wall_time=$(awk -v e="$end_time" -v s="$start_time" 'BEGIN{printf "%.2f", e-s}') |
| 141 | + |
| 142 | + # Extract the specific benchmark time from JSON file |
| 143 | + bench_time_ns=$(extract_bench_time "$bench_json_file" "$BENCH_NAME") |
| 144 | + |
| 145 | + if [ -z "$bench_time_ns" ] || [ "$bench_time_ns" = "0" ]; then |
| 146 | + echo -e "${RED}Warning: Could not extract timing for '$BENCH_NAME' from JSON${NC}" |
| 147 | + echo -e "${YELLOW}Check the JSON file: $bench_json_file${NC}" |
| 148 | + |
| 149 | + # Show what's in the JSON file for debugging |
| 150 | + if [ -f "$bench_json_file" ]; then |
| 151 | + echo -e "${YELLOW}JSON content (first 500 chars):${NC}" |
| 152 | + head -c 500 "$bench_json_file" |
| 153 | + echo "" |
| 154 | + fi |
| 155 | + |
| 156 | + echo "CPUs: $cpu_count - No timing data found" >> "$RESULTS_FILE" |
| 157 | + continue |
| 158 | + fi |
| 159 | + |
| 160 | + # Convert to milliseconds and seconds |
| 161 | + bench_time_ms=$(awk -v ns="$bench_time_ns" 'BEGIN{printf "%.2f", ns / 1000000}') |
| 162 | + bench_time_s=$(awk -v ns="$bench_time_ns" 'BEGIN{printf "%.3f", ns / 1000000000}') |
| 163 | + |
| 164 | + # Calculate speedup and efficiency |
| 165 | + if [ -z "$BASELINE_TIME" ]; then |
| 166 | + BASELINE_TIME="$bench_time_ns" |
| 167 | + speedup="1.00" |
| 168 | + efficiency="100.0" |
| 169 | + else |
| 170 | + speedup=$(awk -v base="$BASELINE_TIME" -v curr="$bench_time_ns" 'BEGIN{printf "%.2f", base / curr}') |
| 171 | + efficiency=$(awk -v sp="$speedup" -v cpus="$cpu_count" 'BEGIN{printf "%.1f", (sp / cpus) * 100}') |
| 172 | + fi |
| 173 | + |
| 174 | + # Store results |
| 175 | + ALL_CPUS+=("$cpu_count") |
| 176 | + ALL_TIMES+=("$bench_time_ms") |
| 177 | + ALL_SPEEDUPS+=("$speedup") |
| 178 | + |
| 179 | + # Write to results file |
| 180 | + echo "CPUs: $cpu_count" >> "$RESULTS_FILE" |
| 181 | + echo " Time: ${bench_time_ms} ms (${bench_time_s} s)" >> "$RESULTS_FILE" |
| 182 | + echo " Speedup: ${speedup}x" >> "$RESULTS_FILE" |
| 183 | + echo " Efficiency: ${efficiency}%" >> "$RESULTS_FILE" |
| 184 | + echo " Wall time: ${wall_time}s" >> "$RESULTS_FILE" |
| 185 | + echo "" >> "$RESULTS_FILE" |
| 186 | + |
| 187 | + # Write to CSV |
| 188 | + echo "$cpu_count,$bench_time_ms,$bench_time_s,$speedup,$efficiency" >> "$CSV_FILE" |
| 189 | + |
| 190 | + # Display results |
| 191 | + echo -e "${GREEN}✓ Completed${NC}" |
| 192 | + echo -e " ${CYAN}Time for '$BENCH_NAME':${NC} ${bench_time_ms} ms" |
| 193 | + echo -e " ${CYAN}Speedup:${NC} ${speedup}x" |
| 194 | + echo -e " ${CYAN}Efficiency:${NC} ${efficiency}%" |
| 195 | + echo "" |
| 196 | +done |
| 197 | + |
| 198 | +# Generate summary |
| 199 | +echo -e "${GREEN}╔════════════════════════════════════════════════════════════════╗${NC}" |
| 200 | +echo -e "${GREEN}║ SUMMARY ║${NC}" |
| 201 | +echo -e "${GREEN}╚════════════════════════════════════════════════════════════════╝${NC}" |
| 202 | +echo "" |
| 203 | + |
| 204 | +# Print table header |
| 205 | +printf "${CYAN}%-8s %-15s %-12s %-12s${NC}\n" "CPUs" "Time (ms)" "Speedup" "Efficiency" |
| 206 | +printf "${CYAN}%-8s %-15s %-12s %-12s${NC}\n" "────" "──────────" "───────" "──────────" |
| 207 | + |
| 208 | +# Print results table |
| 209 | +for i in "${!ALL_CPUS[@]}"; do |
| 210 | + cpu="${ALL_CPUS[$i]}" |
| 211 | + time="${ALL_TIMES[$i]}" |
| 212 | + speedup="${ALL_SPEEDUPS[$i]}" |
| 213 | + |
| 214 | + if [ "$i" -eq 0 ]; then |
| 215 | + efficiency="100.0%" |
| 216 | + else |
| 217 | + efficiency=$(awk -v sp="$speedup" -v cpus="$cpu" 'BEGIN{printf "%.1f%%", (sp / cpus) * 100}') |
| 218 | + fi |
| 219 | + |
| 220 | + # Color code based on efficiency |
| 221 | + if [ "$i" -eq 0 ]; then |
| 222 | + color="${GREEN}" |
| 223 | + else |
| 224 | + eff_val=$(echo "$efficiency" | sed 's/%//') |
| 225 | + if (( $(echo "$eff_val > 75" | bc -l) )); then |
| 226 | + color="${GREEN}" |
| 227 | + elif (( $(echo "$eff_val > 50" | bc -l) )); then |
| 228 | + color="${YELLOW}" |
| 229 | + else |
| 230 | + color="${RED}" |
| 231 | + fi |
| 232 | + fi |
| 233 | + |
| 234 | + printf "${color}%-8s %-15s %-12s %-12s${NC}\n" "$cpu" "$time" "${speedup}x" "$efficiency" |
| 235 | +done |
| 236 | + |
| 237 | +echo "" |
| 238 | +echo -e "${MAGENTA}═══════════════════════════════════════════════════════════════${NC}" |
| 239 | +echo "" |
| 240 | + |
| 241 | +# Generate scaling plot (ASCII art) |
| 242 | +echo -e "${CYAN}Scaling Visualization:${NC}" |
| 243 | +echo "" |
| 244 | + |
| 245 | +if [ "${#ALL_TIMES[@]}" -gt 0 ]; then |
| 246 | + # Find max time for scaling |
| 247 | + max_time=$(printf '%s\n' "${ALL_TIMES[@]}" | sort -rn | head -1) |
| 248 | + |
| 249 | + # Create ASCII bar chart |
| 250 | + for i in "${!ALL_CPUS[@]}"; do |
| 251 | + cpu="${ALL_CPUS[$i]}" |
| 252 | + time="${ALL_TIMES[$i]}" |
| 253 | + |
| 254 | + # Calculate bar length (max 50 chars) |
| 255 | + bar_len=$(awk -v t="$time" -v m="$max_time" 'BEGIN{printf "%.0f", (t/m) * 50}') |
| 256 | + |
| 257 | + # Create bar |
| 258 | + bar="" |
| 259 | + for ((j=0; j<bar_len; j++)); do |
| 260 | + bar="${bar}█" |
| 261 | + done |
| 262 | + |
| 263 | + printf "%-6s │%s %.2f ms\n" "${cpu} CPU" "$bar" "$time" |
| 264 | + done |
| 265 | +fi |
| 266 | + |
| 267 | +echo "" |
| 268 | +echo -e "${GREEN}Results saved to:${NC}" |
| 269 | +echo " - Summary: $RESULTS_FILE" |
| 270 | +echo " - CSV: $CSV_FILE" |
| 271 | +echo " - Logs: $OUTPUT_DIR/run_*cpus/output.log" |
| 272 | +echo "" |
| 273 | + |
| 274 | +# Check for scaling issues |
| 275 | +if [ "${#ALL_SPEEDUPS[@]}" -gt 1 ]; then |
| 276 | + last_speedup="${ALL_SPEEDUPS[-1]}" |
| 277 | + last_cpu="${ALL_CPUS[-1]}" |
| 278 | + actual_efficiency=$(awk -v sp="$last_speedup" -v cpus="$last_cpu" 'BEGIN{printf "%.1f", (sp / cpus) * 100}') |
| 279 | + |
| 280 | + if (( $(echo "$actual_efficiency < 50" | bc -l) )); then |
| 281 | + echo -e "${YELLOW}⚠ Warning: Poor scaling detected!${NC}" |
| 282 | + echo -e " At ${last_cpu} CPUs: ${actual_efficiency}% efficiency" |
| 283 | + echo -e " Consider investigating thread contention or memory bottlenecks." |
| 284 | + elif (( $(echo "$actual_efficiency < 75" | bc -l) )); then |
| 285 | + echo -e "${YELLOW}Note: Moderate scaling efficiency at high CPU counts.${NC}" |
| 286 | + echo -e " At ${last_cpu} CPUs: ${actual_efficiency}% efficiency" |
| 287 | + else |
| 288 | + echo -e "${GREEN}✓ Good scaling efficiency maintained!${NC}" |
| 289 | + echo -e " At ${last_cpu} CPUs: ${actual_efficiency}% efficiency" |
| 290 | + fi |
| 291 | +fi |
| 292 | + |
| 293 | +echo "" |
0 commit comments