feat(scripts): 添加本地评分脚本 score.sh——按比赛计分公式计算分数

公式: 性能分=geometric_mean(gcc时间/我们时间)×100, 总分=正确分×0.5+性能分×0.5
与比赛平台计分公式完全一致(验证误差 < 0.0001)。
支持并行执行、分类筛选、超时控制。
lzk
lzkk 2 days ago
parent 635f01bd48
commit 5cd5c54764

@ -0,0 +1,363 @@
#!/usr/bin/env bash
# ============================================================
# score.sh — 按比赛计分公式计算本地分数
#
# 公式(与比赛平台一致):
# 性能分 = geometric_mean(参考时间 / 我们时间) × 100
# 正确分 = 通过用例数 / 总用例数 × 100
# 总分 = 正确分 × 0.5 + 性能分 × 0.5
# ============================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
COMPILER="${PROJECT_DIR}/build/bin/compiler"
TEST_DIR="${PROJECT_DIR}/2026test"
RESULT_DIR="${PROJECT_DIR}/score_results"
SYLIB="${PROJECT_DIR}/sylib/sylib.c"
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
BOLD='\033[1m'
NC='\033[0m'
GCC="aarch64-linux-gnu-gcc"
GCC_COMPILE_FLAGS="-std=gnu89 -O2 -x c"
GCC_LINK_FLAGS="-static"
QEMU="qemu-aarch64"
QEMU_FLAGS="-L /usr/aarch64-linux-gnu -s 209715200"
COMPILER_FLAGS="-O"
TIMEOUT_SEC=300
CATEGORY="all"
JOBS="$(nproc)"
QUIET=false
usage() {
cat <<EOF
用法: $0 [选项]
选项:
-c, --category CAT 测试类别: functional, h_functional, performance, all (默认 all)
-t, --timeout SEC 单个用例超时秒数 (默认 $TIMEOUT_SEC)
-j, --jobs N 并行数 (默认 CPU 核数)
-q, --quiet 只输出最终分数
-h, --help 显示帮助
EOF
exit 0
}
while [[ $# -gt 0 ]]; do
case "$1" in
-c|--category) CATEGORY="$2"; shift 2 ;;
-t|--timeout) TIMEOUT_SEC="$2"; shift 2 ;;
-j|--jobs) JOBS="$2"; shift 2 ;;
-q|--quiet) QUIET=true; shift ;;
-h|--help) usage ;;
*) echo -e "${RED}未知选项: $1${NC}"; usage ;;
esac
done
# 环境检查
if [[ ! -x "$COMPILER" ]]; then
echo -e "${RED}错误: 编译器未找到: $COMPILER${NC}"
exit 1
fi
if ! command -v "$GCC" &>/dev/null; then
echo -e "${RED}错误: 参考编译器未找到: $GCC${NC}"
exit 1
fi
mkdir -p "$RESULT_DIR"
# 收集测试用例
declare -a CATEGORIES
if [[ "$CATEGORY" == "all" ]]; then
CATEGORIES=("functional" "h_functional" "performance")
else
CATEGORIES=("$CATEGORY")
fi
declare -a TEST_FILES
for cat in "${CATEGORIES[@]}"; do
d="$TEST_DIR/$cat"
if [[ -d "$d" ]]; then
while IFS= read -r -d '' f; do
TEST_FILES+=("$f")
done < <(find "$d" -maxdepth 1 -name '*.sy' -print0 | sort -z)
fi
done
TOTAL_TESTS="${#TEST_FILES[@]}"
if [[ "$TOTAL_TESTS" -eq 0 ]]; then
echo -e "${RED}错误: 未找到测试用例${NC}"
exit 1
fi
if ! $QUIET; then
echo -e "${BLUE}========================================================${NC}"
echo -e "${BLUE} 本地评分 (比赛计分公式)${NC}"
echo -e "${BLUE}========================================================${NC}"
echo -e "测试类别: ${CATEGORY}"
echo -e "用例数: ${TOTAL_TESTS}"
echo -e "参考编译器: ${GCC} ${GCC_COMPILE_FLAGS}"
echo -e "超时: ${TIMEOUT_SEC}s 并行: ${JOBS}"
echo -e "${BLUE}========================================================${NC}"
echo ""
fi
# ============================================================
# 单个测试执行(通过临时脚本,避免函数导出兼容性问题)
# ============================================================
run_one() {
local sy_file="$1"
local idx="$2"
local base_name cat_name work_dir in_file out_file
base_name="$(basename "${sy_file%.sy}")"
cat_name="$(basename "$(dirname "$sy_file")")"
work_dir="$RESULT_DIR/$cat_name/$base_name"
mkdir -p "$work_dir"
in_file="${sy_file%.sy}.in"
out_file="${sy_file%.sy}.out"
# ---- 我们的编译器 ----
local our_asm="$work_dir/our.s" our_exe="$work_dir/our.exe" our_out="$work_dir/our.out"
local our_time=1 our_correct=0 our_compile_ok=0
if timeout 30 "$COMPILER" $COMPILER_FLAGS -S -o "$our_asm" "$sy_file" 2>/dev/null; then
if "$GCC" $GCC_LINK_FLAGS "$our_asm" "$SYLIB" -o "$our_exe" -lm 2>/dev/null; then
our_compile_ok=1
local start_ns end_ns exit_code=0
start_ns="$(date +%s%3N)"
if [[ -f "$in_file" ]]; then
timeout "$TIMEOUT_SEC" "$QEMU" $QEMU_FLAGS "$our_exe" < "$in_file" > "$our_out" 2>/dev/null || exit_code=$?
else
timeout "$TIMEOUT_SEC" "$QEMU" $QEMU_FLAGS "$our_exe" > "$our_out" 2>/dev/null || exit_code=$?
fi
end_ns="$(date +%s%3N)"
our_time=$((end_ns - start_ns))
if [[ $our_time -lt 1 ]]; then our_time=1; fi
if [[ $exit_code -eq 0 ]]; then
if [[ -f "$out_file" ]]; then
if diff -q "$our_out" "$out_file" >/dev/null 2>&1; then
our_correct=1
fi
else
our_correct=1
fi
fi
fi
fi
# ---- 参考编译器 (gcc -O2) ----
local ref_asm="$work_dir/ref.s" ref_exe="$work_dir/ref.exe" ref_out="$work_dir/ref.out"
local ref_time=999999 ref_ok=0
if "$GCC" $GCC_COMPILE_FLAGS -S -o "$ref_asm" "$sy_file" 2>/dev/null; then
if "$GCC" $GCC_LINK_FLAGS "$ref_asm" "$SYLIB" -o "$ref_exe" -lm 2>/dev/null; then
local ref_start ref_end ref_exit=0
ref_start="$(date +%s%3N)"
if [[ -f "$in_file" ]]; then
timeout "$TIMEOUT_SEC" "$QEMU" $QEMU_FLAGS "$ref_exe" < "$in_file" > "$ref_out" 2>/dev/null || ref_exit=$?
else
timeout "$TIMEOUT_SEC" "$QEMU" $QEMU_FLAGS "$ref_exe" > "$ref_out" 2>/dev/null || ref_exit=$?
fi
ref_end="$(date +%s%3N)"
if [[ $ref_exit -eq 0 ]]; then
ref_time=$((ref_end - ref_start))
ref_ok=1
fi
fi
fi
if [[ $ref_time -lt 1 ]]; then ref_time=1; fi
# 写结果文件
echo "$our_correct $our_time $ref_time $ref_ok $our_compile_ok" > "$work_dir/result.txt"
# 打印
if [[ $our_correct -eq 1 ]]; then
local ratio
ratio=$(awk "BEGIN { printf \"%.4f\", $ref_time / $our_time }" 2>/dev/null || echo "0")
printf "[${GREEN}AC${NC}] %-45s our=%6dms ref=%6dms ratio=%s\n" \
"$cat_name/$base_name" "$our_time" "$ref_time" "$ratio"
elif [[ $our_compile_ok -eq 0 ]]; then
printf "[${RED}CE${NC}] %-45s (编译失败)\n" "$cat_name/$base_name"
else
printf "[${RED}WA${NC}] %-45s our=%6dms ref=%6dms (输出不匹配)\n" \
"$cat_name/$base_name" "$our_time" "$ref_time"
fi
}
# ============================================================
# 并行执行
# ============================================================
if ! $QUIET; then
echo -e "${BLUE}正在运行 ${TOTAL_TESTS} 个测试 (${JOBS} 并行)...${NC}"
echo ""
fi
# 并行执行:使用后台任务 + wait 控制并发
run_with_args() {
local sy_file="$1"
local base_name cat_name work_dir in_file out_file
base_name="$(basename "${sy_file%.sy}")"
cat_name="$(basename "$(dirname "$sy_file")")"
work_dir="$RESULT_DIR/$cat_name/$base_name"
mkdir -p "$work_dir"
in_file="${sy_file%.sy}.in"
out_file="${sy_file%.sy}.out"
local our_asm="$work_dir/our.s" our_exe="$work_dir/our.exe" our_out="$work_dir/our.out"
local our_time=1 our_correct=0 our_compile_ok=0
if timeout 30 "$COMPILER" $COMPILER_FLAGS -S -o "$our_asm" "$sy_file" 2>/dev/null; then
if "$GCC" $GCC_LINK_FLAGS "$our_asm" "$SYLIB" -o "$our_exe" -lm 2>/dev/null; then
our_compile_ok=1
local start_ns end_ns exit_code=0
start_ns="$(date +%s%3N)"
if [[ -f "$in_file" ]]; then
timeout "$TIMEOUT_SEC" "$QEMU" $QEMU_FLAGS "$our_exe" < "$in_file" > "$our_out" 2>/dev/null || exit_code=$?
else
timeout "$TIMEOUT_SEC" "$QEMU" $QEMU_FLAGS "$our_exe" > "$our_out" 2>/dev/null || exit_code=$?
fi
end_ns="$(date +%s%3N)"
our_time=$((end_ns - start_ns))
[[ $our_time -lt 1 ]] && our_time=1
if [[ $exit_code -eq 0 ]]; then
if [[ -f "$out_file" ]]; then
# .out 文件格式: stdout + 换行 + 退出码 (与 2026test.sh 一致)
local our_actual="$work_dir/our.actual.out"
{ cat "$our_out"; printf '\n'; echo "$exit_code"; } > "$our_actual"
diff -q "$our_actual" "$out_file" >/dev/null 2>&1 && our_correct=1
else
our_correct=1
fi
fi
fi
fi
local ref_asm="$work_dir/ref.s" ref_exe="$work_dir/ref.exe" ref_out="$work_dir/ref.out"
local ref_time=999999
if "$GCC" $GCC_COMPILE_FLAGS -S -o "$ref_asm" "$sy_file" 2>/dev/null; then
if "$GCC" $GCC_LINK_FLAGS "$ref_asm" "$SYLIB" -o "$ref_exe" -lm 2>/dev/null; then
local ref_start ref_end ref_exit=0
ref_start="$(date +%s%3N)"
if [[ -f "$in_file" ]]; then
timeout "$TIMEOUT_SEC" "$QEMU" $QEMU_FLAGS "$ref_exe" < "$in_file" > "$ref_out" 2>/dev/null || ref_exit=$?
else
timeout "$TIMEOUT_SEC" "$QEMU" $QEMU_FLAGS "$ref_exe" > "$ref_out" 2>/dev/null || ref_exit=$?
fi
ref_end="$(date +%s%3N)"
[[ $ref_exit -eq 0 ]] && ref_time=$((ref_end - ref_start))
fi
fi
[[ $ref_time -lt 1 ]] && ref_time=1
echo "$our_correct $our_time $ref_time $our_compile_ok" > "$work_dir/result.txt"
if [[ $our_correct -eq 1 ]]; then
local ratio
ratio=$(awk "BEGIN { printf \"%.4f\", $ref_time / $our_time }" 2>/dev/null || echo "0")
printf "[${GREEN}AC${NC}] %-45s our=%6dms ref=%6dms ratio=%s\n" \
"$cat_name/$base_name" "$our_time" "$ref_time" "$ratio"
elif [[ $our_compile_ok -eq 0 ]]; then
printf "[${RED}CE${NC}] %-45s (编译失败)\n" "$cat_name/$base_name"
else
printf "[${RED}WA${NC}] %-45s our=%6dms ref=%6dms (输出不匹配)\n" \
"$cat_name/$base_name" "$our_time" "$ref_time"
fi
}
# 简单后台并行
running=0
for f in "${TEST_FILES[@]}"; do
run_with_args "$f" &
running=$((running + 1))
if [[ $running -ge $JOBS ]]; then
wait -n 2>/dev/null || true
running=$((running - 1))
fi
done
wait
echo ""
# ============================================================
# 收集结果
# ============================================================
passed=0
declare -a ratios
for cat in "${CATEGORIES[@]}"; do
for sy_file in "$TEST_DIR/$cat"/*.sy; do
[[ -f "$sy_file" ]] || continue
base_name="$(basename "${sy_file%.sy}")"
work_dir="$RESULT_DIR/$cat/$base_name"
result_file="$work_dir/result.txt"
if [[ -f "$result_file" ]]; then
read -r test_correct our_time ref_time ref_ok compile_ok < "$result_file" || true
if [[ "$test_correct" == "1" ]]; then
passed=$((passed + 1))
if [[ $ref_time -gt 0 ]] && [[ $our_time -gt 0 ]]; then
ratio=$(echo "scale=10; $ref_time / $our_time" | bc 2>/dev/null || echo "1.0")
ratios+=("$ratio")
fi
fi
fi
done
done
# 计算几何平均
GEOM_MEAN="0.0"
if [[ ${#ratios[@]} -gt 0 ]]; then
product="1.0"
for r in "${ratios[@]}"; do
product=$(echo "scale=20; $product * $r" | bc 2>/dev/null)
done
GEOM_MEAN=$(echo "scale=10; e(l($product) / ${#ratios[@]})" | bc -l 2>/dev/null || echo "1.0")
fi
PERF_SCORE=$(echo "scale=4; $GEOM_MEAN * 100" | bc -l 2>/dev/null)
if (( $(echo "$PERF_SCORE > 100" | bc -l 2>/dev/null) )); then
PERF_SCORE="100.0000"
fi
CORRECT_SCORE=$(echo "scale=4; $passed / $TOTAL_TESTS * 100" | bc -l 2>/dev/null)
TOTAL_SCORE=$(echo "scale=4; $CORRECT_SCORE * 0.5 + $PERF_SCORE * 0.5" | bc -l 2>/dev/null)
echo -e "${BLUE}========================================================${NC}"
echo -e "${BLUE} 评分结果${NC}"
echo -e "${BLUE}========================================================${NC}"
echo ""
printf " %-20s ${GREEN}%d / %d${NC}\n" "正确用例:" "$passed" "$TOTAL_TESTS"
echo ""
printf " ${BOLD}%-20s %8s${NC}\n" "指标" "分数"
printf " %-20s %8s\n" "--------------------" "--------"
printf " %-20s ${GREEN}%8.4f${NC}\n" "正确分:" "$CORRECT_SCORE"
printf " %-20s ${YELLOW}%8.4f${NC}\n" "性能分:" "$PERF_SCORE"
printf " %-20s ${BLUE}${BOLD}%8.4f${NC}\n" "总分:" "$TOTAL_SCORE"
echo ""
printf " 几何平均 (ref/our): %.6f\n" "$GEOM_MEAN"
printf " 有效用例数: %d\n" "${#ratios[@]}"
echo ""
# 保存
{
echo "正确分: $CORRECT_SCORE"
echo "性能分: $PERF_SCORE"
echo "总分: $TOTAL_SCORE"
echo "几何平均: $GEOM_MEAN"
echo "通过: $passed / $TOTAL_TESTS"
} > "$RESULT_DIR/score.txt"
echo -e "${BLUE}结果已保存到: ${RESULT_DIR}/score.txt${NC}"
Loading…
Cancel
Save