L.Leverage Learn从第一行代码,到读懂真实后端查看项目源码

OJ 评测系统 / 第 20 课

编译、运行与资源限制

追踪提交代码如何变成可运行程序,并区分限制配置和评测时实际观测到的资源数据。

◷ 预计 16 分钟 · 面向初学者

提交进入队列后,Worker 会先按语言选择编译步骤,再对测试输入启动运行容器。编译检查源代码并生成可执行产物;运行才把测试数据交给用户程序。把两个阶段分开,才能判断错误来自语法/工具链,还是来自程序行为。

固定版本的 DockerSandbox.prepare 为 Python 执行 python3 -m py_compile,JavaScript 执行 node --check,TypeScript 调用 tsc,C/C++ 则调用对应的编译器。语言标识不是编译命令本身:历史提交语言数字 9 映射为 Python,运行时语言名再映射到这组受支持的语言之一。若编译返回用户代码错误,评测引擎将其记录为 CE;容器创建、资源不足或工件丢失等基础设施问题应归入 SE,不能把环境坏了伪装成代码写错。

提交源码 ──编译容器──> 编译产物 ──运行容器 + 单个测试输入──> 输出与退出状态
   CE / SE                    AC候选 / WA / RE / TLE / MLE / OLE / SE

每阶段的预算不同。源码编译容器在这份实现里配置为 30 秒、512 MiB;运行阶段则接收题目经过语言倍率处理后的时间与内存限制,以及输出字节上限。运行容器配置 Docker 内存上限和相同的 swap 上限、--cpus=0.5,并由主机侧计时器超时终止。它们是限制配置,说明容器被要求遵守什么边界,不代表运行实际消耗恰好达到该数值。不要把编译预算误读成每道题的运行时间,也不要把容器限制写成测量报告。

“实际用时”同样需要看定义。当前沙箱记录从启动脚本发出 READY 标志到观察到进程退出的 wall time,不是 CPU 时间;wallMs 还包含容器创建、连接与清理。内存值来自 Docker stats 的周期采样,并经过缓存调整,不是精确峰值 RSS;如果没有可靠测量,cpuTimeMs 不会报告。主机调度、测量间隔和启动开销都可能影响显示值。评测引擎聚合测试点的已观测最大值,缺失数据仍然缺失,不应该补成零。

结果还看退出状态和输出:退出码为零才可能是成功,非零通常是 RE;被判定 OOM 的容器会转成 MLE;超过输出字节预算会成为 OLE。普通比较器逐行去掉行尾空格并忽略末尾空行,但保留行首空格,所以 7 与 7 不相等。看见 WA 时,检查输出比较和测试数据;看见 TLE/MLE 时,分别核对生效预算和实际状态;不要仅凭控制台一个数字推断用户程序用了多少 CPU。

还要留意限制作用的范围:单个容器的内存上限,并不等于整台 Worker 的可用内存;同一时间的容器数量、编译与运行是否并发,也会影响主机压力。输出限制按进程输出流累计,stderr 诊断信息同样不能无限收集。题目配置经过语言倍率和全局上限处理后才成为运行参数,所以排查“本地能过、线上超时”时,应先确认真正传给沙箱的参数,再检查测量定义、数据规模和主机负载,而不是机械地把时间限制调大。

小练习

某提交显示 CE,另一个显示 SE。哪一个通常指向源码编译失败?运行记录的 executionMs 能否当作 CPU 毫秒?

**答案:**CE 通常表示源码未通过编译;SE 表示沙箱或评测基础设施失败等非用户代码错误。executionMs 是 READY 到退出的观测墙钟时间,不是 CPU 时间。

去真实仓库看看

链接固定到公开提交 de34b268e75f293e904d7734c194a4ff2467b7a8;这里的资源数字均来自引用实现:

互动练习:区分编译与运行限制

一次提交的请求选择 language 数字标识 9;编译在编译时限内成功,但程序 READY 之后的运行耗时超过题目设置的墙钟时间上限。应归到哪个阶段?

选择一个答案

✓
这一课,你已经能……

编译阶段和运行阶段使用不同进程预算;时间、内存限制是控制参数,展示的用时与内存则受测量方式影响。