做一个 Rust 优化 quiz,背后其实是一堂工程课 本文是对 Engineering a Rust optimization quiz 改写和讲解内容覆盖原文主线与关键技术点不是逐字翻译。内容结构概览为什么要做一个 Rust 优化问答游戏题目设计从“冷知识”到真正能教学的题目Rust 编译器优化里的几个反直觉案例为什么作者选择自己造一个 quiz 系统Dioxus、Markdown 幻灯片与 Rust 前端体验房间、投票、服务端状态与实时同步部署、压测与现场前的连续修 bug从这个项目学到的工程经验结语优化题不是为了炫技而是训练判断力做一个 Rust 优化 quiz背后其实是一堂工程课Rust 社区里不缺 quiz。很多题目专门考语言边角、drop 顺序、生命周期细节甚至故意设计得“不公平”。但 fasterthanlime 这篇文章讲的不是“我又做了一个刁钻 Rust 测验”而是一个更有意思的问题如果要在 EuroRust 2025 的主舞台上让几百名观众同时参与一场 Rust 优化问答要怎样设计题目、系统和现场流程这件事最后变成了一个完整的小型工程项目题库设计、Compiler Explorer 验证、Rust 前端框架选型、实时投票、房间码、部署、压测、OAuth、断线重连、手机遥控翻页以及最后 40 分钟前还在部署。它表面上是一场 quiz实际上是一篇关于工程取舍的文章。一、题目不是为了难而是为了让人学到东西作者一开始想收集一些 Rust 和 Cargo 的“诅咒知识”也就是那些听起来很怪、很冷、很容易让人答错的细节。但他很快发现冷知识不一定适合做题。好的 quiz 题目应该满足一个条件哪怕观众答错了也能学到东西。所以题目逐渐从“你知不知道某个奇怪事实”变成了“你能不能判断编译器会怎样优化这段代码”。比如最基础的问题是fnf(x:u64)-u64{x/2}很多人会猜它会优化成右移fnf(x:u64)-u64{x1}这个例子简单、直观适合作为入口。但真正有意思的地方在后面。当除数不是 2而是 5 时编译器并不会真的生成一个昂贵的除法指令。它可能会把除以常数改写成一次乘法和移位x/5会被优化成类似“乘以一个魔数再右移”的形式。这类题的价值在于它让观众意识到优化不是“把代码换成看起来更底层的写法”那么简单。现代编译器知道的技巧往往比我们手写的微优化更多。二、Compiler Explorer 是题库设计的核心工具为了确认每一道题“实际会被优化成什么样”作者大量使用 Compiler Explorer。Compiler Explorer 的作用不是让你背汇编而是让你观察“源码、优化级别、后端、目标平台”之间的关系。作者原本还想比较不同后端比如 LLVM、GCC、Cranelift但因为时间有限这一版 quiz 没有做多答案支持所以最后主要围绕一个编译结果来设计。这里有一个重要启发技术 quiz 如果要严肃就不能只靠直觉出题。出题人需要验证答案而且最好能解释为什么。尤其是编译器优化题因为很多答案都不是“语言层面必然如此”而是“这个编译器在这个上下文下这么做”。三、浮点优化为什么(x / 3.0) / 0.0不能简化文章里最精彩的技术点之一是一组浮点算术题。比如x/1.0可以优化成x再比如(x/1.0)/0.0也可以理解为x/0.0但下面这个就不行(x/3.0)/0.0不能简单优化为x/0.0原因和浮点数里的 NaN 有关。如果x是一个非常小的非零数先除以3.0之后结果可能因为舍入变成0.0。此时再除以0.0就可能得到 NaN。而如果直接做x / 0.0结果可能不是同一个值。所以这里的关键不是代数上“看起来可以化简”而是 IEEE 浮点语义下舍入、零、无穷和 NaN 都会影响优化是否合法。这也是编译器优化最容易被低估的地方优化不是把数学式子变短而是在不改变可观察行为的前提下改写程序。四、为什么有些循环不会被识别成 memcpy另一个技术点是 memcpy 识别。看起来下面这种循环很像拷贝foriin0..dst.len(){dst[i]src[i];}编译器可能把它识别成memcpy。但作者提到一个变体循环里先写 0再写源数据。foriin0..dst.len(){dst[i]0;dst[i]src[i];}从人类视角看第一行写 0 是无用赋值后面马上被覆盖。我们会想编译器应该先删掉无用赋值再识别成 memcpy。但实际并不一定如此。原因在于 LLVM 的优化 pass 有顺序。文章里提到这里的问题是“识别 memcpy”的 pass 发生在“移除无用赋值”的 pass 之后于是错过了最佳时机。这说明一件事编译器不是一个一次性理解全局语义的神谕。它是一条优化流水线每个 pass 在特定时机看见特定形状的 IR。所以“理论上可以优化”和“实际上被优化”之间可能隔着 pass 顺序、IR 形态和工程成本。五、为什么不用现成软件而要自己造等题库足够支撑 30 分钟现场互动之后作者开始写软件。他承认市面上有很多演示软件、quiz 软件。但他想要的东西比较明确主持人创建房间观众用手机加入每道题实时投票大屏展示题目和结果主持人可以前进、后退状态由服务端统一保存Rust 技术栈尽量贯穿到底于是项目变成了一个试验 Rust 前端框架的机会。他选择了 Dioxus并使用 0.7.0 release candidate主要是想体验更快的开发反馈能力。体验并不完全顺滑。简单改动时很快但热更新不够稳定。很多时候仍然需要等待重新编译再回到浏览器恢复状态。和 Svelte Vite 这种前端开发体验相比Rust 前端生态的迭代速度还有距离。但 Rust 的优势也很明显类型系统、编译期信心、可以复用已有经验和库比如 Markdown 解析、语法高亮等。这就是一次典型的工程取舍Rust 前端不一定让你写得最快但它让你在复杂度上升时更有把握。六、用 Markdown 写幻灯片为了快速迭代作者一开始想把幻灯片硬编码进程序里后来发现这会严重拖慢题目和版式调整。于是他设计了一个简单格式整个 quiz 是一个 Markdown 文件每张 slide 用---分隔。有题目的 slide 使用带 checkbox 的有序列表选中的项就是正确答案。大概是这样的结构# Title --- Question? 1. [x] Yes 1. [ ] Maybe 1. [ ] No --- Results slide这里有个小技巧Markdown 有序列表不一定要写1, 2, 3全写1.也可以正常渲染编号。这个设计很朴素但非常实用。它把内容创作从代码里解放出来让题库可以快速调整。公众号读者如果做内部培训、技术分享、现场互动也可以借鉴这个思路先别急着设计复杂后台先把内容格式设计成可以高频修改的形式。七、服务端状态简单粗暴但足够可靠这个 quiz 系统和普通幻灯片不同所有状态都放在服务端。主持人创建房间。观众加入房间。主持人翻页、观众投票服务端会把整个房间状态广播给所有人。理论上更高效的做法是只广播 diff也就是只发送变化的部分。但作者没有这样做。原因很现实时间不够而且全量状态更容易保证正确。这是很重要的工程判断。很多时候系统不是死在“不够高效”而是死在“不够可靠”。对于一场 30 分钟的现场活动全量广播只要扛得住正确性和可预测性比精致的增量协议更重要。安全方面也做得很轻量主持人入口用硬编码密码保护玩家不能自己起名字而是随机生成四字母房间码会过滤脏词避免大屏幕上出现尴尬内容。这些都不是完美安全但符合场景需求。八、一个现场 bug返回上一页后题目被锁作者设计了一个规则如果切换到某张题目 slide并且这张题还没有任何投票那就允许投票。这个规则在 50 人规模的 playtest 中工作得很好。但正式现场出现了问题主持人翻到下一题后有一个人很快投了票。随后主持人返回上一页结果这道题因为已经有了投票被系统判定为不再开放其他观众无法继续投。这是全场反馈最多的问题。这个 bug 的有趣之处在于它不是传统意义上的代码崩溃而是交互规则在真实现场节奏下暴露了边界条件。playtest 能发现很多问题但现场永远会给你新的组合。九、部署cargo-chef 不一定总是有用当应用能跑起来后作者写了 Dockerfile并尝试遵循常见 Rust Docker 构建最佳实践多阶段构建、使用 cargo-chef 预构建依赖。但在 Dioxus 项目里这套方案并没有带来预期收益。Dioxus 使用自己的 target 目录导致 cargo-chef 提前做的工作没有很好复用。最后作者删掉 cargo-chef改用 Docker cache mount 来缓存构建目录。对他的本地构建来说这反而更快。这里的经验很值得记“最佳实践”不是咒语。每个框架、工具链、构建系统都有自己的路径和缓存模型。照搬最佳实践之前要验证它是否真的改善了你的场景。十、压测不能剧透于是又做了一套题正式会议前作者担心几百人同时在线会不会把系统打爆于是决定做一次公开压测。但 quiz 题目不能提前泄露所以他必须临时做第二套题。他把 spreadsheet 里剩下的题拿出来又去翻 Rust 编译器文档化的错误信息从里面找适合出题的点。这次压测在 Twitch 上进行大约有 40 名玩家持续参与聊天区也因为题目产生了不少争论。这说明技术 quiz 的一个核心魅力好题目不是让人沉默而是让人开始讨论“为什么”。十一、D-2Rust meetup 上继续试题EuroRust 前两天作者参加了巴黎的 Rust meetup。他做了 Dioxus 相关分享然后把测试 quiz 带到现场。这次现场测试非常热闹也继续暴露问题。其中一个关键 bug 是手机睡眠后重连。重连之后系统给玩家分配了新的随机 ID导致玩家丢失分数。这类问题在本地开发时不容易出现因为开发者不会像真实观众一样锁屏、切后台、断网、重连。所以实时互动系统一定要把“移动端弱连接”当作基本环境而不是异常情况。十二、D-1修重连、加二维码、补解释会议前一天作者修了玩家重连问题也让主持人可以重连。这非常关键。想象一下如果主持人在台上不小心刷新页面整个房间状态和分数都没了那就真的是灾难。他还加了二维码让观众在演讲开始前就能扫码进入房间。同一天另一个 playtest 帮他补上了前面浮点题的解释也就是(x / 3.0) / 0.0不能优化成x / 0.0的原因。另一个 playtester 则解释了 memcpy 识别问题不是编译器不知道而是优化 pass 的顺序导致错过了。这部分说明题目不是写完就结束。好的技术题需要反复验证、打磨解释、让不同背景的人试答。十三、正式当天GitHub 登录和手机遥控正式当天作者仍然在补功能。他发现随机生成的名字虽然安全但不利于展示排行榜。大家如果答得好当然希望知道是谁。于是他加了 GitHub 登录。实现并不严密profile 信息存在客户端一侧理论上可以伪造。但在现场场景里这已经足够。他还让手机端也能显示题目方便观众投票。另外由于现场没有翻页器他把手机变成了主持人遥控器主持人可以加入已有房间并通过滑动手势切换 slide。这是一种很现场的工程不是为了架构漂亮而是为了让活动顺利发生。十四、自动重连的希望以及最后的兜底Dioxus 0.7.0-rc.1 当时宣传了 WebSocket 自动重连能力作者临时升级希望它能解决连接稳定性问题。但测试下来还不够。于是他加了更直接的兜底逻辑如果 WebSocket 出错或者 ping 几秒内没有收到 pong就刷新页面。这不是最优雅的方案但在现场前一天它是合理方案。工程现场常常如此优雅方案需要时间兜底方案需要判断力。十五、Showtime最后一次部署在上台前 40 分钟最后一次部署发生在上台前 40 分钟。然后一切顺利。现场观众玩得很开心。作者也被问到是否会开源这套 quiz 软件。他的回答是可以但希望先稍微完善一下。这次经历也让他重新燃起了上台演讲的兴趣并计划在 RustLab 2025 继续做类似的 Rust quiz。十六、这篇文章真正想讲什么这篇文章表面上讲 Rust 优化 quiz实际上讲了三件事。第一技术内容要服务于学习而不是服务于炫技。好的 Rust 优化题不是让人记住一个神秘答案而是让人理解编译器为什么能优化、为什么不能优化、什么时候直觉会失效。第二工程不是选最先进的方案而是在限制条件下做能工作的系统。全量广播状态、硬编码主持人密码、随机玩家名、Docker cache mount、页面刷新式重连这些都不“完美”但它们符合时间、场景和风险。第三现场软件的敌人不是代码本身而是真实世界。手机会睡眠网络会断主持人可能刷新页面观众会抢先投票房间码可能组成尴尬单词最后一次部署可能发生在上台前 40 分钟。这些事情不在类型系统里但都在工程里。结语优化题不是背答案而是训练边界感Rust 编译器和 LLVM 能做很多优化但它们不是魔法。整数除法可以变成乘法和移位。浮点表达式不能随便按实数代数化简。看似显然的 memcpy也可能因为优化 pass 顺序而没有发生。看似简单的 quiz 系统也会在现场环境中暴露状态管理和连接稳定性问题。这篇文章最有价值的地方是把“编译器优化”从抽象知识变成了一次真实工程实践。它告诉我们理解优化不是为了写更花哨的代码。理解优化是为了知道什么时候该相信编译器什么时候该验证什么时候该承认现实比模型复杂。