| 1 | #!/usr/bin/env bash |
| 2 | set -e |
| 3 | |
| 4 | echo "# Running benchmarks..." |
| 5 | echo "" |
| 6 | |
| 7 | BENCH_DIR="$(pwd)/tests/benchmark" |
| 8 | IRC_BIN="$(pwd)/build/nix-irc" |
| 9 | |
| 10 | GREEN='\033[0;32m' |
| 11 | BLUE='\033[0;34m' |
| 12 | YELLOW='\033[0;33m' |
| 13 | NC='\033[0m' |
| 14 | |
| 15 | get_ms() { |
| 16 | local time_str="$1" |
| 17 | if [[ $time_str =~ ([0-9]+)m([0-9.]+)s ]]; then |
| 18 | local mins="${BASH_REMATCH[1]}" |
| 19 | local secs="${BASH_REMATCH[2]}" |
| 20 | local ms |
| 21 | ms=$(awk "BEGIN {printf \"%.1f\", ($mins * 60000) + ($secs * 1000)}") |
| 22 | echo "$ms" |
| 23 | else |
| 24 | echo "0" |
| 25 | fi |
| 26 | } |
| 27 | |
| 28 | run_benchmark() { |
| 29 | local name="$1" |
| 30 | local file="$2" |
| 31 | |
| 32 | echo -e "${BLUE}=== $name ===${NC}" |
| 33 | echo "" |
| 34 | |
| 35 | # Measure compilation time only |
| 36 | echo -n " Compilation only: " |
| 37 | local compile_start |
| 38 | compile_start=$(date +%s%N) |
| 39 | "$IRC_BIN" "$file" /tmp/bench.nixir >/dev/null 2>&1 |
| 40 | local compile_end |
| 41 | compile_end=$(date +%s%N) |
| 42 | local compile_ms=$(((compile_end - compile_start) / 1000000)) |
| 43 | echo -e "${YELLOW}${compile_ms}ms${NC}" |
| 44 | |
| 45 | # Measure IR loading only (deserialization + evaluation) |
| 46 | echo -n " IR load only: " |
| 47 | PLUGIN_PATH="$(pwd)/build/nix-ir-plugin.so" |
| 48 | if [ ! -f "$PLUGIN_PATH" ]; then |
| 49 | echo -e "${YELLOW}skipped${NC} (plugin not built)" |
| 50 | else |
| 51 | # Pre-compile the IR |
| 52 | "$IRC_BIN" "$file" /tmp/bench.nixir >/dev/null 2>&1 |
| 53 | |
| 54 | # Measure just the loading (average of 10 runs to reduce noise) |
| 55 | local total_load_us=0 |
| 56 | for _ in {1..10}; do |
| 57 | local load_output |
| 58 | load_output=$(nix-instantiate --plugin-files "$PLUGIN_PATH" --eval --expr "builtins.nixIR_loadIR \"/tmp/bench.nixir\"" 2>&1 >/dev/null | grep "nixIR timing" | grep -oP 'total=\K[0-9]+') |
| 59 | total_load_us=$((total_load_us + load_output)) |
| 60 | done |
| 61 | local avg_load_us=$((total_load_us / 10)) |
| 62 | local avg_load_ms_frac=$(awk "BEGIN {printf \"%.3f\", $avg_load_us / 1000}") |
| 63 | echo -e "${GREEN}${avg_load_ms_frac}ms${NC} avg (10 runs)" |
| 64 | fi |
| 65 | |
| 66 | # Measure full pipeline (compile + nix-instantiate overhead + IR load) |
| 67 | echo -n " Full pipeline: " |
| 68 | if [ ! -f "$PLUGIN_PATH" ]; then |
| 69 | echo -e "${YELLOW}skipped${NC}" |
| 70 | else |
| 71 | local pipeline_start |
| 72 | pipeline_start=$(date +%s%N) |
| 73 | "$IRC_BIN" "$file" /tmp/bench.nixir >/dev/null 2>&1 |
| 74 | nix-instantiate --plugin-files "$PLUGIN_PATH" --eval --expr "builtins.nixIR_loadIR \"/tmp/bench.nixir\"" >/dev/null 2>&1 |
| 75 | local pipeline_end |
| 76 | pipeline_end=$(date +%s%N) |
| 77 | local pipeline_ms=$(((pipeline_end - pipeline_start) / 1000000)) |
| 78 | echo -e "${YELLOW}${pipeline_ms}ms${NC}" |
| 79 | fi |
| 80 | |
| 81 | # Source and IR sizes |
| 82 | local src_size |
| 83 | src_size=$(stat -c%s "$file" 2>/dev/null || stat -f%z "$file" 2>/dev/null) |
| 84 | local ir_size |
| 85 | ir_size=$(stat -c%s /tmp/bench.nixir 2>/dev/null || stat -f%z /tmp/bench.nixir 2>/dev/null) |
| 86 | local ratio=$((ir_size * 100 / src_size)) |
| 87 | echo -e " Source size: ${src_size}B" |
| 88 | echo -e " IR bundle size: ${ir_size}B (${ratio}% of source)" |
| 89 | |
| 90 | echo "" |
| 91 | |
| 92 | # Native Nix evaluation (baseline) |
| 93 | echo -n " Native Nix eval: " |
| 94 | local native_total=0 |
| 95 | for _ in {1..5}; do |
| 96 | local t |
| 97 | t=$( (time nix-instantiate --eval --strict "$file" >/dev/null 2>&1) 2>&1 | grep "real" | awk '{print $2}') |
| 98 | local ms |
| 99 | ms=$(get_ms "$t") |
| 100 | native_total=$(awk "BEGIN {print $native_total + $ms}") |
| 101 | done |
| 102 | local native_avg |
| 103 | native_avg=$(awk "BEGIN {printf \"%.1f\", $native_total / 5}") |
| 104 | echo -e "${GREEN}${native_avg}ms${NC} avg (5 runs)" |
| 105 | |
| 106 | echo "" |
| 107 | } |
| 108 | |
| 109 | echo "Measuring IR compilation speed and bundle size characteristics." |
| 110 | echo "" |
| 111 | |
| 112 | run_benchmark "Simple Expression" "$BENCH_DIR/simple.nix" |
| 113 | run_benchmark "Medium Complexity" "$BENCH_DIR/medium.nix" |
| 114 | run_benchmark "Large Expression" "$BENCH_DIR/large.nix" |
| 115 | |
| 116 | # Overall statistics |
| 117 | echo -e "${BLUE}=== Overall Statistics ===${NC}" |
| 118 | echo "" |
| 119 | |
| 120 | testdir=$(mktemp -d) |
| 121 | total_nix=0 |
| 122 | total_ir=0 |
| 123 | total_compile_time=0 |
| 124 | |
| 125 | for f in "$BENCH_DIR"/*.nix; do |
| 126 | nixsize=$(stat -c%s "$f" 2>/dev/null || stat -f%z "$f" 2>/dev/null) |
| 127 | base=$(basename "$f" .nix) |
| 128 | irfile="${testdir}/${base}.nixir" |
| 129 | |
| 130 | start=$(date +%s%N) |
| 131 | "$IRC_BIN" "$f" "$irfile" >/dev/null 2>&1 |
| 132 | end=$(date +%s%N) |
| 133 | compile_time=$(((end - start) / 1000000)) |
| 134 | |
| 135 | if [ -f "$irfile" ]; then |
| 136 | irsize=$(stat -c%s "$irfile" 2>/dev/null || stat -f%z "$irfile" 2>/dev/null) |
| 137 | total_nix=$((total_nix + nixsize)) |
| 138 | total_ir=$((total_ir + irsize)) |
| 139 | total_compile_time=$((total_compile_time + compile_time)) |
| 140 | fi |
| 141 | done |
| 142 | |
| 143 | total_ratio=$((total_ir * 100 / total_nix)) |
| 144 | avg_compile_time=$((total_compile_time / 3)) |
| 145 | |
| 146 | # TBH those are entirely unnecessary. However, I'm a sucker for data |
| 147 | # and those are trivial to compile. Might as well. Who knows, maybe it'll |
| 148 | # come in handy in the future. |
| 149 | echo " Total source size: ${total_nix}B" |
| 150 | echo " Total IR size: ${total_ir}B" |
| 151 | echo " Compression ratio: ${total_ratio}% of source" |
| 152 | echo " Average compile time: ${avg_compile_time}ms" |
| 153 | echo "" |
| 154 | |
| 155 | rm -rf "$testdir" |