|
|
#!/usr/bin/env bash
|
|
|
# 差分测试:编译器 vs gcc,支持正确性对比和性能对比
|
|
|
# 用法:
|
|
|
# ./scripts/diff_test.sh --baseline 生成 gcc 正确性基线
|
|
|
# ./scripts/diff_test.sh --diff 正确性差分对比(输出是否一致)
|
|
|
# ./scripts/diff_test.sh --perf 性能对比(指令数)
|
|
|
# ./scripts/diff_test.sh --perf --gcc-opt 2 性能对比 vs gcc -O2
|
|
|
# ./scripts/diff_test.sh --perf --save-asm 性能对比并保存 gcc 汇编
|
|
|
# ./scripts/diff_test.sh --perf --gcc-opt 0 对比 gcc -O0(最低基线)
|
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
RED='\033[0;31m'
|
|
|
GREEN='\033[0;32m'
|
|
|
YELLOW='\033[1;33m'
|
|
|
CYAN='\033[0;36m'
|
|
|
BOLD='\033[1m'
|
|
|
BLUE='\033[0;34m'
|
|
|
NC='\033[0m'
|
|
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
|
TEST_ROOT="$PROJECT_ROOT/2026test"
|
|
|
RESULTS_ROOT="$PROJECT_ROOT/2026test_results"
|
|
|
GCC_BASELINE_DIR="$RESULTS_ROOT/gcc_baseline"
|
|
|
GCC_ASM_DIR="$RESULTS_ROOT/gcc_asm"
|
|
|
RUNTIME_SRC="$PROJECT_ROOT/sylib/sylib.c"
|
|
|
RUNTIME_OBJ="$PROJECT_ROOT/build/test_runtime/sylib_gcc.o"
|
|
|
COMPILER="$PROJECT_ROOT/build/bin/compiler"
|
|
|
|
|
|
GCC="aarch64-linux-gnu-gcc"
|
|
|
QEMU="qemu-aarch64"
|
|
|
|
|
|
CORRECTNESS_CATS=("functional" "h_functional")
|
|
|
PERF_CATS=("performance")
|
|
|
DO_BASELINE=false
|
|
|
DO_DIFF=false
|
|
|
DO_PERF=false
|
|
|
SAVE_ASM=false
|
|
|
GCC_OPT_LEVEL=3
|
|
|
MAX_CASES=0
|
|
|
REPORT_FILE=""
|
|
|
JSON_FILE=""
|
|
|
JOBS=$(nproc 2>/dev/null || echo 4)
|
|
|
|
|
|
usage() {
|
|
|
cat <<'EOF'
|
|
|
用法: ./scripts/diff_test.sh [选项]
|
|
|
|
|
|
差分测试:对每个 .sy 用例,用编译器生成汇编和 gcc 编译后,
|
|
|
比较输出(正确性)或指令数(性能)。
|
|
|
|
|
|
模式(至少选一个):
|
|
|
--baseline 生成 gcc 正确性基线(保存输出到 gcc_baseline/)
|
|
|
--diff 正确性差分对比(编译器输出 vs gcc 基线)
|
|
|
--perf 性能对比(编译器 vs gcc 指令数及比值)
|
|
|
|
|
|
性能对比选项(与 --perf 配合):
|
|
|
--gcc-opt <N> gcc 优化级别 0/1/2/3(默认: 3,最高标准优化)
|
|
|
--save-asm 保存 gcc 汇编到 gcc_asm/ 供人工分析
|
|
|
--perf-cats <cats> 性能对比覆盖的类别,逗号分隔
|
|
|
(默认: performance,也可加 functional,h_functional)
|
|
|
--report <file> 导出性能对比结果到 CSV 文件
|
|
|
--json <file> 导出性能对比结果到 JSON 文件
|
|
|
|
|
|
通用选项:
|
|
|
-n, --max N 最多运行 N 个用例 (0=不限制,默认: 0)
|
|
|
-j, --jobs N 并行任务数 (默认: nproc,设为1恢复串行)
|
|
|
-h, --help 显示此帮助信息
|
|
|
|
|
|
输出目录:
|
|
|
2026test_results/gcc_baseline/ gcc 正确性基线(输出 + 退出码)
|
|
|
2026test_results/gcc_asm/ gcc 汇编(--save-asm 时保存)
|
|
|
EOF
|
|
|
}
|
|
|
|
|
|
# ============================================================
|
|
|
# 参数解析
|
|
|
# ============================================================
|
|
|
while [[ $# -gt 0 ]]; do
|
|
|
case "$1" in
|
|
|
--baseline) DO_BASELINE=true ;;
|
|
|
--diff) DO_DIFF=true ;;
|
|
|
--perf) DO_PERF=true ;;
|
|
|
--save-asm) SAVE_ASM=true ;;
|
|
|
--gcc-opt) GCC_OPT_LEVEL="$2"; shift ;;
|
|
|
--perf-cats) IFS=',' read -ra PERF_CATS <<< "$2"; shift ;;
|
|
|
--report) REPORT_FILE="$2"; shift ;;
|
|
|
--json) JSON_FILE="$2"; shift ;;
|
|
|
-n|--max) MAX_CASES="$2"; shift ;;
|
|
|
-j|--jobs) JOBS="$2"
|
|
|
if ! [[ "$JOBS" =~ ^[0-9]+$ ]] || [[ "$JOBS" -lt 1 ]]; then
|
|
|
echo "错误: --jobs 需要正整数"; exit 1
|
|
|
fi ;;
|
|
|
-h|--help) usage; exit 0 ;;
|
|
|
*) echo "未知选项: $1"; usage; exit 1 ;;
|
|
|
esac
|
|
|
shift
|
|
|
done
|
|
|
|
|
|
if [[ "$DO_BASELINE" == false && "$DO_DIFF" == false && "$DO_PERF" == false ]]; then
|
|
|
echo "错误: 至少需要 --baseline、--diff 或 --perf 之一"
|
|
|
usage
|
|
|
exit 1
|
|
|
fi
|
|
|
|
|
|
if ! [[ "$GCC_OPT_LEVEL" =~ ^[0-3]$ ]]; then
|
|
|
echo "错误: --gcc-opt 必须是 0/1/2/3"
|
|
|
exit 1
|
|
|
fi
|
|
|
|
|
|
# ============================================================
|
|
|
# 预检
|
|
|
# ============================================================
|
|
|
command -v "$GCC" >/dev/null 2>&1 || { echo "未找到 $GCC"; exit 1; }
|
|
|
command -v "$QEMU" >/dev/null 2>&1 || { echo "未找到 $QEMU"; exit 1; }
|
|
|
[[ -f "$RUNTIME_SRC" ]] || { echo "未找到 $RUNTIME_SRC"; exit 1; }
|
|
|
[[ -f "$COMPILER" ]] || { echo "未找到编译器 $COMPILER,请先构建"; exit 1; }
|
|
|
|
|
|
# 编译运行时库
|
|
|
mkdir -p "$(dirname "$RUNTIME_OBJ")"
|
|
|
if [[ ! -f "$RUNTIME_OBJ" || "$RUNTIME_SRC" -nt "$RUNTIME_OBJ" ]]; then
|
|
|
echo "编译运行时库 $RUNTIME_SRC → $RUNTIME_OBJ ..."
|
|
|
$GCC -O2 -c "$RUNTIME_SRC" -o "$RUNTIME_OBJ"
|
|
|
fi
|
|
|
|
|
|
# ============================================================
|
|
|
# SysY → C 预处理(让 gcc 能编译 SysY 特有问题)
|
|
|
# ============================================================
|
|
|
preprocess_for_gcc() {
|
|
|
local src="$1"
|
|
|
local dst="$2"
|
|
|
python3 -c "
|
|
|
import re, sys
|
|
|
with open('$src') as f:
|
|
|
content = f.read()
|
|
|
content = re.sub(r'const int (\w+) = ([^;]+);', r'#define \1 \2', content)
|
|
|
with open('$dst', 'w') as f:
|
|
|
f.write(content)
|
|
|
"
|
|
|
}
|
|
|
|
|
|
# ============================================================
|
|
|
# 规范化比较(替代 python3,减少进程启动开销)
|
|
|
# ============================================================
|
|
|
canon_compare() {
|
|
|
local expected="$1" actual="$2"
|
|
|
diff -q \
|
|
|
<(sed 's/\r$//; s/[[:space:]]*$//' "$expected" \
|
|
|
| awk '{lines[NR]=$0} END{last=NR; while(last>0&&lines[last]=="")last--; for(i=1;i<=last;i++)print lines[i]}') \
|
|
|
<(sed 's/\r$//; s/[[:space:]]*$//' "$actual" \
|
|
|
| awk '{lines[NR]=$0} END{last=NR; while(last>0&&lines[last]=="")last--; for(i=1;i<=last;i++)print lines[i]}') \
|
|
|
> /dev/null 2>&1
|
|
|
}
|
|
|
|
|
|
# ============================================================
|
|
|
# 收集用例
|
|
|
# ============================================================
|
|
|
collect_cases() {
|
|
|
local cats=("$@")
|
|
|
local cases=()
|
|
|
for cat in "${cats[@]}"; do
|
|
|
local dir="$TEST_ROOT/$cat"
|
|
|
[[ -d "$dir" ]] || continue
|
|
|
for sy in "$dir"/*.sy; do
|
|
|
[[ -f "$sy" ]] || continue
|
|
|
cases+=("$sy")
|
|
|
done
|
|
|
done
|
|
|
printf '%s\n' "${cases[@]}" | sort
|
|
|
}
|
|
|
|
|
|
load_cases() {
|
|
|
local cats=("$@")
|
|
|
CASES=()
|
|
|
while IFS= read -r line; do
|
|
|
CASES+=("$line")
|
|
|
done < <(collect_cases "${cats[@]}")
|
|
|
|
|
|
if [[ "$MAX_CASES" -gt 0 && "$MAX_CASES" -lt "${#CASES[@]}" ]]; then
|
|
|
CASES=("${CASES[@]:0:$MAX_CASES}")
|
|
|
fi
|
|
|
}
|
|
|
|
|
|
# ============================================================
|
|
|
# 生成 gcc 正确性基线(并行化)
|
|
|
# ============================================================
|
|
|
run_baseline_worker() {
|
|
|
local idx="$1" sy="$2"
|
|
|
local result_file="$3"
|
|
|
|
|
|
local dir=$(dirname "$sy")
|
|
|
local cat=$(basename "$dir")
|
|
|
local base=$(basename "$sy")
|
|
|
local stem=${base%.sy}
|
|
|
local out_dir="$GCC_BASELINE_DIR/$cat"
|
|
|
local exe="$out_dir/$stem"
|
|
|
local actual_file="$out_dir/$stem.actual.out"
|
|
|
local stdin_file="$dir/$stem.in"
|
|
|
|
|
|
mkdir -p "$out_dir"
|
|
|
|
|
|
local gcc_src=$(mktemp /tmp/gcc_baseline_XXXX.sy)
|
|
|
preprocess_for_gcc "$sy" "$gcc_src"
|
|
|
|
|
|
local status="FAIL"
|
|
|
if $GCC -x c "$gcc_src" -x none "$RUNTIME_OBJ" -static -o "$exe" -lm 2>/dev/null; then
|
|
|
rm -f "$gcc_src"
|
|
|
local exit_code=0
|
|
|
set +e
|
|
|
if [[ -f "$stdin_file" ]]; then
|
|
|
timeout --signal=KILL 60 "$QEMU" "$exe" < "$stdin_file" > "$out_dir/$stem.stdout" 2>/dev/null || exit_code=$?
|
|
|
else
|
|
|
timeout --signal=KILL 60 "$QEMU" "$exe" < /dev/null > "$out_dir/$stem.stdout" 2>/dev/null || exit_code=$?
|
|
|
fi
|
|
|
set -e
|
|
|
|
|
|
{
|
|
|
cat "$out_dir/$stem.stdout"
|
|
|
if [[ -s "$out_dir/$stem.stdout" ]] && (( $(tail -c 1 "$out_dir/$stem.stdout" | wc -l) == 0 )); then
|
|
|
printf '\n'
|
|
|
fi
|
|
|
printf '%s\n' "$exit_code"
|
|
|
} > "$actual_file"
|
|
|
status="OK"
|
|
|
else
|
|
|
rm -f "$gcc_src"
|
|
|
fi
|
|
|
|
|
|
printf 'STATUS=%s\nNAME=%s\n' "$status" "$stem" > "$result_file"
|
|
|
}
|
|
|
|
|
|
run_baseline() {
|
|
|
load_cases "${CORRECTNESS_CATS[@]}"
|
|
|
echo ""
|
|
|
echo "========== 生成 gcc 正确性基线(${#CASES[@]} 用例)=========="
|
|
|
|
|
|
local res_dir="$GCC_BASELINE_DIR/.results"
|
|
|
mkdir -p "$res_dir"
|
|
|
|
|
|
if [[ $JOBS -gt 1 && ${#CASES[@]} -gt 1 ]]; then
|
|
|
export GCC_BASELINE_DIR RUNTIME_OBJ GCC QEMU
|
|
|
export -f run_baseline_worker preprocess_for_gcc
|
|
|
|
|
|
declare -a QUEUE=()
|
|
|
for i in "${!CASES[@]}"; do
|
|
|
QUEUE+=("$i|${CASES[$i]}")
|
|
|
done
|
|
|
|
|
|
printf '%s\n' "${QUEUE[@]}" | xargs -P "$JOBS" -L 1 bash -c '
|
|
|
IFS="|" read -r idx sy <<< "$1"
|
|
|
run_baseline_worker "$idx" "$sy" "'"$res_dir"'/$idx"
|
|
|
' _
|
|
|
else
|
|
|
for i in "${!CASES[@]}"; do
|
|
|
run_baseline_worker "$i" "${CASES[$i]}" "$res_dir/$i"
|
|
|
done
|
|
|
fi
|
|
|
|
|
|
# 汇总
|
|
|
local total=0 pass=0 fail=0
|
|
|
for i in "${!CASES[@]}"; do
|
|
|
total=$((total + 1))
|
|
|
if [[ -f "$res_dir/$i" ]]; then
|
|
|
local status name
|
|
|
status=$(grep '^STATUS=' "$res_dir/$i" | cut -d= -f2)
|
|
|
name=$(grep '^NAME=' "$res_dir/$i" | cut -d= -f2)
|
|
|
if [[ "$status" == "OK" ]]; then
|
|
|
pass=$((pass + 1))
|
|
|
printf " [${GREEN}OK${NC}] %-35s (%d/%d)\r" "$name" "$total" "${#CASES[@]}"
|
|
|
else
|
|
|
fail=$((fail + 1))
|
|
|
echo -e " [${RED}FAIL${NC}] $name (gcc 编译失败)"
|
|
|
fi
|
|
|
else
|
|
|
fail=$((fail + 1))
|
|
|
fi
|
|
|
done
|
|
|
|
|
|
rm -rf "$res_dir"
|
|
|
printf '\n'
|
|
|
echo "基线完成: $pass/$total 成功"
|
|
|
if [[ $fail -gt 0 ]]; then
|
|
|
echo -e " ${YELLOW}$fail 个 gcc 编译失败(可能使用了 gcc 不支持的 SysY 语法)${NC}"
|
|
|
fi
|
|
|
}
|
|
|
|
|
|
# ============================================================
|
|
|
# 正确性差分对比
|
|
|
# ============================================================
|
|
|
run_diff() {
|
|
|
load_cases "${CORRECTNESS_CATS[@]}"
|
|
|
echo ""
|
|
|
echo "========== 正确性差分对比(${#CASES[@]} 用例)=========="
|
|
|
|
|
|
local total=0 match=0 mismatch=0 skip=0
|
|
|
|
|
|
for sy in "${CASES[@]}"; do
|
|
|
total=$((total + 1))
|
|
|
|
|
|
local dir=$(dirname "$sy")
|
|
|
local cat=$(basename "$dir")
|
|
|
local base=$(basename "$sy")
|
|
|
local stem=${base%.sy}
|
|
|
|
|
|
local compiler_out="$RESULTS_ROOT/$cat/$stem.actual.out"
|
|
|
local gcc_out="$GCC_BASELINE_DIR/$cat/$stem.actual.out"
|
|
|
|
|
|
if [[ ! -f "$compiler_out" ]]; then
|
|
|
echo -e " [${YELLOW}SKIP${NC}] $stem (无编译器输出,先跑 2026test.sh)"
|
|
|
skip=$((skip + 1))
|
|
|
continue
|
|
|
fi
|
|
|
|
|
|
if [[ ! -f "$gcc_out" ]]; then
|
|
|
echo -e " [${YELLOW}SKIP${NC}] $stem (无 gcc 基线,先跑 --baseline)"
|
|
|
skip=$((skip + 1))
|
|
|
continue
|
|
|
fi
|
|
|
|
|
|
if canon_compare "$compiler_out" "$gcc_out"; then
|
|
|
match=$((match + 1))
|
|
|
printf " [${GREEN}MATCH${NC}] %-35s (%d/%d)\r" "$stem" "$total" "${#CASES[@]}"
|
|
|
else
|
|
|
mismatch=$((mismatch + 1))
|
|
|
printf '\n'
|
|
|
echo -e " [${RED}MISMATCH${NC}] $stem"
|
|
|
echo " --- 编译器输出 ---"
|
|
|
cat "$compiler_out" | head -20 | sed 's/^/ | /'
|
|
|
echo " --- gcc 输出 ---"
|
|
|
cat "$gcc_out" | head -20 | sed 's/^/ | /'
|
|
|
echo " --- diff ---"
|
|
|
diff -u <(cat "$compiler_out") <(cat "$gcc_out") | head -20 | sed 's/^/ | /' || true
|
|
|
echo ""
|
|
|
fi
|
|
|
done
|
|
|
|
|
|
printf '\n'
|
|
|
echo "========== 正确性差分结果 =========="
|
|
|
echo -e " 匹配: ${GREEN}$match${NC}"
|
|
|
echo -e " 不匹配: ${RED}$mismatch${NC}"
|
|
|
if [[ $skip -gt 0 ]]; then
|
|
|
echo -e " 跳过: ${YELLOW}$skip${NC}"
|
|
|
fi
|
|
|
|
|
|
if [[ $mismatch -eq 0 ]]; then
|
|
|
echo -e "\n${GREEN}全部匹配,编译器输出与 gcc -O0 一致${NC}"
|
|
|
fi
|
|
|
}
|
|
|
|
|
|
# ============================================================
|
|
|
# 性能对比(并行化)
|
|
|
# ============================================================
|
|
|
run_perf_worker() {
|
|
|
local idx="$1" sy="$2" result_file="$3"
|
|
|
|
|
|
local dir=$(dirname "$sy")
|
|
|
local cat=$(basename "$dir")
|
|
|
local base=$(basename "$sy")
|
|
|
local stem=${base%.sy}
|
|
|
|
|
|
local compiler_asm=$(mktemp /tmp/compiler_XXXX.s)
|
|
|
local gcc_asm=$(mktemp /tmp/gcc_XXXX.s)
|
|
|
|
|
|
# 编译器生成汇编
|
|
|
local comp_ok=true compiler_lines=0
|
|
|
if ! timeout --signal=KILL 60 "$COMPILER" -S -O -o "$compiler_asm" "$sy" 2>/dev/null; then
|
|
|
comp_ok=false
|
|
|
else
|
|
|
compiler_lines=$(wc -l < "$compiler_asm")
|
|
|
compiler_lines=${compiler_lines:-0}
|
|
|
fi
|
|
|
|
|
|
# gcc 生成汇编
|
|
|
local gcc_ok=true gcc_lines=0
|
|
|
local gcc_src=$(mktemp /tmp/gcc_perf_XXXX.sy)
|
|
|
preprocess_for_gcc "$sy" "$gcc_src"
|
|
|
if ! $GCC -x c -S "-O${GCC_OPT_LEVEL}" -o "$gcc_asm" "$gcc_src" 2>/dev/null; then
|
|
|
gcc_ok=false
|
|
|
else
|
|
|
gcc_lines=$(wc -l < "$gcc_asm")
|
|
|
gcc_lines=${gcc_lines:-0}
|
|
|
fi
|
|
|
rm -f "$gcc_src"
|
|
|
|
|
|
# 保存 gcc 汇编
|
|
|
if [[ "$SAVE_ASM" == true && "$gcc_ok" == true ]]; then
|
|
|
local save_dir="$GCC_ASM_DIR/${cat}/${GCC_OPT_LEVEL}"
|
|
|
mkdir -p "$save_dir"
|
|
|
cp "$gcc_asm" "$save_dir/${stem}.s"
|
|
|
fi
|
|
|
|
|
|
rm -f "$compiler_asm" "$gcc_asm"
|
|
|
|
|
|
printf 'STATUS=%s\nSTEM=%s\nCAT=%s\nCOMPILER_LINES=%s\nGCC_LINES=%s\n' \
|
|
|
"$(if $comp_ok && $gcc_ok; then echo "OK"; elif ! $comp_ok; then echo "COMP_FAIL"; else echo "GCC_FAIL"; fi)" \
|
|
|
"$stem" "$cat" "$compiler_lines" "$gcc_lines" \
|
|
|
> "$result_file"
|
|
|
}
|
|
|
|
|
|
run_perf() {
|
|
|
load_cases "${PERF_CATS[@]}"
|
|
|
|
|
|
local gcc_opt="-O${GCC_OPT_LEVEL}"
|
|
|
local gcc_label="gcc ${gcc_opt}"
|
|
|
|
|
|
echo ""
|
|
|
echo "========== 性能对比:编译器 -O vs ${gcc_label}(${#CASES[@]} 用例)=========="
|
|
|
echo ""
|
|
|
|
|
|
local res_dir="$RESULTS_ROOT/.perf_results"
|
|
|
rm -rf "$res_dir"
|
|
|
mkdir -p "$res_dir"
|
|
|
|
|
|
# 并行或串行执行
|
|
|
if [[ $JOBS -gt 1 && ${#CASES[@]} -gt 1 ]]; then
|
|
|
export COMPILER GCC GCC_OPT_LEVEL SAVE_ASM GCC_ASM_DIR
|
|
|
export -f run_perf_worker preprocess_for_gcc
|
|
|
|
|
|
declare -a QUEUE=()
|
|
|
for i in "${!CASES[@]}"; do
|
|
|
QUEUE+=("$i|${CASES[$i]}")
|
|
|
done
|
|
|
|
|
|
printf '%s\n' "${QUEUE[@]}" | xargs -P "$JOBS" -L 1 bash -c '
|
|
|
IFS="|" read -r idx sy <<< "$1"
|
|
|
run_perf_worker "$idx" "$sy" "'"$res_dir"'/$idx"
|
|
|
' _
|
|
|
else
|
|
|
for i in "${!CASES[@]}"; do
|
|
|
run_perf_worker "$i" "${CASES[$i]}" "$res_dir/$i"
|
|
|
done
|
|
|
fi
|
|
|
|
|
|
# 汇总
|
|
|
local total=${#CASES[@]}
|
|
|
local compiler_fail=0 gcc_fail=0
|
|
|
local -a results=() # "stem|compiler_lines|gcc_lines|ratio"
|
|
|
|
|
|
for i in "${!CASES[@]}"; do
|
|
|
local rf="$res_dir/$i"
|
|
|
|
|
|
if [[ ! -f "$rf" ]]; then
|
|
|
compiler_fail=$((compiler_fail + 1))
|
|
|
echo -e " [${RED}FAIL${NC}] $(basename "${CASES[$i]}" .sy) 超时/崩溃"
|
|
|
continue
|
|
|
fi
|
|
|
|
|
|
local status stem cat cl gl
|
|
|
status=$(grep '^STATUS=' "$rf" | cut -d= -f2)
|
|
|
stem=$(grep '^STEM=' "$rf" | cut -d= -f2)
|
|
|
cl=$(grep '^COMPILER_LINES=' "$rf" | cut -d= -f2)
|
|
|
gl=$(grep '^GCC_LINES=' "$rf" | cut -d= -f2)
|
|
|
|
|
|
case "$status" in
|
|
|
COMP_FAIL)
|
|
|
compiler_fail=$((compiler_fail + 1))
|
|
|
echo -e " [${RED}FAIL${NC}] $stem 编译器编译失败"
|
|
|
;;
|
|
|
GCC_FAIL)
|
|
|
gcc_fail=$((gcc_fail + 1))
|
|
|
echo -e " [${YELLOW}SKIP${NC}] $stem gcc 编译失败"
|
|
|
;;
|
|
|
OK)
|
|
|
local ratio flag=""
|
|
|
if [[ "$gl" -eq 0 ]]; then
|
|
|
ratio="N/A"
|
|
|
else
|
|
|
ratio=$(awk -v c="$cl" -v g="$gl" 'BEGIN { printf "%.2f", c/g }')
|
|
|
fi
|
|
|
|
|
|
if [[ "$ratio" != "N/A" ]]; then
|
|
|
if awk -v r="$ratio" 'BEGIN { exit(r <= 1.5 ? 0 : 1) }'; then
|
|
|
flag="${GREEN}"
|
|
|
elif awk -v r="$ratio" 'BEGIN { exit(r <= 3.0 ? 0 : 1) }'; then
|
|
|
flag="${YELLOW}"
|
|
|
else
|
|
|
flag="${RED}"
|
|
|
fi
|
|
|
fi
|
|
|
|
|
|
printf " %-35s 编译器:%5d gcc:%5d ${flag}${BOLD}%sx${NC}\n" \
|
|
|
"$stem" "$cl" "$gl" "$ratio"
|
|
|
|
|
|
results+=("$stem|$cl|$gl|$ratio")
|
|
|
;;
|
|
|
esac
|
|
|
done
|
|
|
|
|
|
rm -rf "$res_dir"
|
|
|
|
|
|
# 汇总统计
|
|
|
printf '\n'
|
|
|
echo "========== 性能对比汇总 =========="
|
|
|
echo ""
|
|
|
|
|
|
local valid=${#results[@]}
|
|
|
|
|
|
if [[ $valid -eq 0 ]]; then
|
|
|
echo "无有效用例"
|
|
|
return
|
|
|
fi
|
|
|
|
|
|
# TOP 5 差距最大
|
|
|
echo "--- 差距最大 TOP 5(优先优化)---"
|
|
|
printf '%s\n' "${results[@]}" | sort -t'|' -k4 -rn | head -5 | while IFS='|' read -r stem cl gl ratio; do
|
|
|
local flag="${RED}"
|
|
|
if awk -v r="$ratio" 'BEGIN { exit(r <= 1.5 ? 0 : 1) }'; then flag="${GREEN}"
|
|
|
elif awk -v r="$ratio" 'BEGIN { exit(r <= 3.0 ? 0 : 1) }'; then flag="${YELLOW}"; fi
|
|
|
printf " %-35s 编译器:%5d gcc:%5d ${flag}${BOLD}%sx${NC}\n" "$stem" "$cl" "$gl" "$ratio"
|
|
|
done
|
|
|
|
|
|
echo ""
|
|
|
echo "--- 差距最小 TOP 5(最接近 1.0x)---"
|
|
|
printf '%s\n' "${results[@]}" | awk -F'|' '
|
|
|
{
|
|
|
ratio = $4 + 0
|
|
|
dist = (ratio > 1.0) ? (ratio - 1.0) : (1.0 - ratio)
|
|
|
printf "%s|%s|%s|%s|%f\n", $1, $2, $3, $4, dist
|
|
|
}' | sort -t'|' -k5 -n | head -5 | while IFS='|' read -r stem cl gl ratio dist; do
|
|
|
printf " %-35s 编译器:%5d gcc:%5d ${GREEN}${BOLD}%sx${NC}\n" "$stem" "$cl" "$gl" "$ratio"
|
|
|
done
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
# 编译器指令数总计
|
|
|
local total_compiler=0 total_gcc=0
|
|
|
for r in "${results[@]}"; do
|
|
|
local cl=$(echo "$r" | cut -d'|' -f2)
|
|
|
local gl=$(echo "$r" | cut -d'|' -f3)
|
|
|
total_compiler=$((total_compiler + cl))
|
|
|
total_gcc=$((total_gcc + gl))
|
|
|
done
|
|
|
|
|
|
# 几何平均
|
|
|
local geo_mean
|
|
|
geo_mean=$(printf '%s\n' "${results[@]}" | awk -F'|' '
|
|
|
BEGIN { sum = 0; n = 0 }
|
|
|
{
|
|
|
ratio = $4 + 0
|
|
|
if (ratio > 0) { sum += log(ratio); n++ }
|
|
|
}
|
|
|
END {
|
|
|
if (n > 0) printf "%.2f", exp(sum / n)
|
|
|
else print "N/A"
|
|
|
}')
|
|
|
|
|
|
echo "--- 整体指标 ---"
|
|
|
printf " 编译器总指令数: %d\n" "$total_compiler"
|
|
|
printf " %s 总指令数: %d\n" "$gcc_label" "$total_gcc"
|
|
|
printf " 总指令数比: ${BOLD}%.2fx${NC}\n" "$(awk -v c="$total_compiler" -v g="$total_gcc" 'BEGIN { printf "%.2f", c/g }')"
|
|
|
printf " 几何平均比: ${BOLD}%sx${NC} (越接近 1.0 越接近 %s)\n" "$geo_mean" "$gcc_label"
|
|
|
printf " 有效用例: %d\n" "$valid"
|
|
|
if [[ $compiler_fail -gt 0 ]]; then
|
|
|
printf " 编译器失败: %d\n" "$compiler_fail"
|
|
|
fi
|
|
|
if [[ $gcc_fail -gt 0 ]]; then
|
|
|
printf " gcc 失败: %d\n" "$gcc_fail"
|
|
|
fi
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
# 性能分估算
|
|
|
local target_ratio="1.11"
|
|
|
if awk -v gm="$geo_mean" -v tr="$target_ratio" 'BEGIN { exit(gm <= tr ? 0 : 1) }'; then
|
|
|
echo -e "${GREEN}几何平均比 ${geo_mean}x ≤ ${target_ratio}x,性能分预估 ≥90(一级水平)${NC}"
|
|
|
else
|
|
|
local perf_est
|
|
|
perf_est=$(awk -v gm="$geo_mean" 'BEGIN { printf "%.0f", 100 / gm }')
|
|
|
echo -e "${YELLOW}几何平均比 ${geo_mean}x > ${target_ratio}x,性能分预估 ≈${perf_est}(一级需 ≥90)${NC}"
|
|
|
fi
|
|
|
|
|
|
if [[ "$SAVE_ASM" == true ]]; then
|
|
|
echo ""
|
|
|
echo -e "${CYAN}gcc 汇编已保存到 $GCC_ASM_DIR/${GCC_OPT_LEVEL}/${NC}"
|
|
|
echo " 可对比分析 gcc 的优化策略(循环展开、向量化、指令调度等)"
|
|
|
fi
|
|
|
|
|
|
# 导出报告
|
|
|
if [[ -n "$REPORT_FILE" ]]; then
|
|
|
_export_csv "$gcc_label" "$GCC_OPT_LEVEL" "$total_compiler" "$total_gcc" "$geo_mean" "$valid" "$compiler_fail" "$gcc_fail"
|
|
|
echo ""
|
|
|
echo -e "${CYAN}CSV 报告已导出到 $REPORT_FILE${NC}"
|
|
|
fi
|
|
|
|
|
|
if [[ -n "$JSON_FILE" ]]; then
|
|
|
_export_json "$gcc_label" "$GCC_OPT_LEVEL" "$total_compiler" "$total_gcc" "$geo_mean" "$valid" "$compiler_fail" "$gcc_fail"
|
|
|
echo ""
|
|
|
echo -e "${CYAN}JSON 报告已导出到 $JSON_FILE${NC}"
|
|
|
fi
|
|
|
}
|
|
|
|
|
|
# ============================================================
|
|
|
# 导出函数
|
|
|
# ============================================================
|
|
|
_export_csv() {
|
|
|
local gcc_label="$1" gcc_opt="$2"
|
|
|
local total_compiler="$3" total_gcc="$4" geo_mean="$5"
|
|
|
local valid="$6" compiler_fail="$7" gcc_fail="$8"
|
|
|
|
|
|
local now=$(date '+%Y-%m-%d %H:%M:%S')
|
|
|
local perf_est=$(awk -v gm="$geo_mean" 'BEGIN { printf "%.0f", 100 / gm }')
|
|
|
|
|
|
{
|
|
|
echo "test_case,category,compiler_insn,gcc_insn,ratio,winner"
|
|
|
printf '%s\n' "${results[@]}" | sort -t'|' -k4 -rn | while IFS='|' read -r stem cl gl ratio; do
|
|
|
local cat=""
|
|
|
for c in "${PERF_CATS[@]}"; do
|
|
|
[[ -f "$TEST_ROOT/$c/${stem}.sy" ]] && { cat="$c"; break; }
|
|
|
done
|
|
|
local winner="gcc"
|
|
|
if awk -v r="$ratio" 'BEGIN { exit(r < 1.0 ? 0 : 1) }'; then winner="compiler"; fi
|
|
|
if [[ "$ratio" == "1.00" ]]; then winner="tie"; fi
|
|
|
echo "${stem},${cat},${cl},${gl},${ratio},${winner}"
|
|
|
done
|
|
|
echo ""
|
|
|
echo "# 汇总,,,"
|
|
|
echo "生成时间,,${now}"
|
|
|
echo "gcc优化级别,,${gcc_opt}"
|
|
|
echo "有效用例,,${valid}"
|
|
|
echo "编译器总指令数,,${total_compiler}"
|
|
|
echo "gcc总指令数,,${total_gcc}"
|
|
|
echo "总指令数比,,${total_compiler}/${total_gcc}"
|
|
|
echo "几何平均比,,${geo_mean}"
|
|
|
echo "性能分预估,,${perf_est}"
|
|
|
} > "$REPORT_FILE"
|
|
|
}
|
|
|
|
|
|
_export_json() {
|
|
|
local gcc_label="$1" gcc_opt="$2"
|
|
|
local total_compiler="$3" total_gcc="$4" geo_mean="$5"
|
|
|
local valid="$6" compiler_fail="$7" gcc_fail="$8"
|
|
|
|
|
|
local now=$(date -Iseconds)
|
|
|
local perf_est=$(awk -v gm="$geo_mean" 'BEGIN { printf "%.0f", 100 / gm }')
|
|
|
|
|
|
python3 - "$JSON_FILE" "$now" "$gcc_opt" "$valid" \
|
|
|
"$total_compiler" "$total_gcc" "$geo_mean" "$perf_est" \
|
|
|
"$compiler_fail" "$gcc_fail" \
|
|
|
"${results[@]}" <<'PY'
|
|
|
import sys, json
|
|
|
|
|
|
outfile = sys.argv[1]
|
|
|
report = {
|
|
|
"generated_at": sys.argv[2],
|
|
|
"gcc_opt_level": int(sys.argv[3]),
|
|
|
"summary": {
|
|
|
"valid_cases": int(sys.argv[4]),
|
|
|
"total_compiler_insn": int(sys.argv[5]),
|
|
|
"total_gcc_insn": int(sys.argv[6]),
|
|
|
"geometric_mean_ratio": float(sys.argv[7]),
|
|
|
"estimated_performance_score": float(sys.argv[8]),
|
|
|
"compiler_fail": int(sys.argv[9]),
|
|
|
"gcc_fail": int(sys.argv[10]),
|
|
|
},
|
|
|
"cases": []
|
|
|
}
|
|
|
|
|
|
for r in sys.argv[11:]:
|
|
|
stem, cl, gl, ratio = r.split('|')
|
|
|
rv = float(ratio)
|
|
|
winner = "compiler" if rv < 1.0 else ("tie" if rv == 1.0 else "gcc")
|
|
|
report["cases"].append({
|
|
|
"test_case": stem,
|
|
|
"compiler_insn": int(cl),
|
|
|
"gcc_insn": int(gl),
|
|
|
"ratio": rv,
|
|
|
"winner": winner
|
|
|
})
|
|
|
|
|
|
with open(outfile, 'w') as f:
|
|
|
json.dump(report, f, ensure_ascii=False, indent=2)
|
|
|
PY
|
|
|
}
|
|
|
|
|
|
# ============================================================
|
|
|
# 执行
|
|
|
# ============================================================
|
|
|
if [[ "$DO_BASELINE" == true ]]; then
|
|
|
run_baseline
|
|
|
fi
|
|
|
|
|
|
if [[ "$DO_DIFF" == true ]]; then
|
|
|
run_diff
|
|
|
fi
|
|
|
|
|
|
if [[ "$DO_PERF" == true ]]; then
|
|
|
run_perf
|
|
|
fi
|