3. 基于 Makefile 的多步骤计算依赖编排
为什么用 Makefile 表达计算依赖
科研计算的多步骤任务本质上是一张文件依赖图:原始数据经预处理生成中间文件,中间文件驱动计算产生结果,结果再被汇总成报告或图。Makefile 的核心语义恰好是“目标文件比它的依赖旧,才执行命令”,因此只要把每个步骤写成“目标: 依赖 + 命令”,就能天然获得增量执行能力——修改某个输入后,只有依赖链下游的目标会被重算。
本例构造一个最小但完整的三步计算链:raw.csv →(预处理)→ clean.csv →(计算)→ result.txt →(汇总)→ summary.md。所有命令用标准 Unix 工具实现,无需额外依赖,可直接复现。
编写 Makefile
在项目目录下创建 Makefile。注意 Makefile 的缩进必须是Tab 字符,不能用空格。下面每个目标都声明了它依赖的文件,命令只做最小必要处理:
# 变量:便于统一修改输入与输出名
RAW := raw.csv
CLEAN := clean.csv
RESULT := result.txt
SUMMARY:= summary.md
# 默认目标:make 不带参数时执行第一个目标
all: $(SUMMARY)
# 预处理:去掉空行与注释行,按第一列排序
$(CLEAN): $(RAW)
@echo "[preprocess] $< -> $@"
grep -v -e '^#' -e '^$$' $< | sort -t, -k1,1n > $@
# 计算:对第二列求和,写入结果文件
$(RESULT): $(CLEAN)
@echo "[compute] $< -> $@"
awk -F, '{s+=$2} END{printf "sum=%.3f\n", s}' $< > $@
# 汇总:把结果包装成 Markdown 报告
$(SUMMARY): $(RESULT)
@echo "[report] $< -> $@"
{ echo "# 计算结果汇总"; echo; echo "数据来源: $(CLEAN)"; echo; cat $<; } > $@
# 清理生成物,便于从头复现
clean:
rm -f $(CLEAN) $(RESULT) $(SUMMARY)
.PHONY: all clean
关键点:$< 是第一个依赖(输入文件),$@ 是目标文件。命令里用 > 重定向到 $@,保证目标文件确实被更新,Make 才能正确比较时间戳。
准备输入并首次执行
创建示例输入 raw.csv(第一列为编号,第二列为测量值):
# id,value
3,1.5
1,2.0
2,3.25
执行 make,预期输出(示例):
[preprocess] raw.csv -> clean.csv
[compute] clean.csv -> result.txt
[report] result.txt -> summary.md
此时 summary.md 内容为:
# 计算结果汇总
数据来源: clean.csv
sum=6.750
验证增量执行
验证 1:无改动时不应重算。 再次执行 make,预期输出:
make: 'summary.md' is up to date.
说明所有目标都比依赖新,Make 跳过了全部命令。
验证 2:只改输入,触发全链重算。 修改 raw.csv 中某行的值(例如把 2,3.25 改为 2,4.00),再执行 make,预期三步全部重新执行,summary.md 中的 sum 变为 7.500。
验证 3:只改中间文件,只重算下游。 先执行 make clean 再 make 生成全部文件;然后手动修改 clean.csv(例如追加一行 4,0.5),执行 make。预期输出只有:
[compute] clean.csv -> result.txt
[report] result.txt -> summary.md
预处理步骤被跳过,因为 raw.csv 未变。这正是“仅重算受影响步骤”的核心行为。
make -n 只打印将执行的命令而不真正执行;用 make -d 可查看时间戳比较的详细日志,适合排查“为什么没重算”的问题。echo 而不重定向),目标文件时间戳不会更新,Make 会认为目标始终过期,导致每次 make 都重复执行。务必确保每条命令都更新 $@。可直接使用的检查清单
| 检查项 | 通过标准 |
|---|---|
| 缩进 | 命令行为 Tab 开头,非空格 |
| 依赖声明 | 每个目标都列出其直接输入文件 |
| 目标更新 | 命令确实写入 $@ |
| 无改动重跑 | make 输出 up to date |
| 改输入重跑 | 仅下游目标被重算 |
| 可清理 | make clean 后能从头复现 |