静态分配与恒定工作:系统设计的定心丸
Static Allocation, Constant Work
我在去年开发限价订单匹配引擎时,曾遭遇一个棘手的 use-after-free 漏洞:被取消的订单释放回内存池时,其链接尚未解除,导致新订单复用该内存时出现逻辑错误。这让我反思:对象池虽能避免类型混淆,但逻辑错误依然存在。文章提出两个核心策略:一是静态分配,即在启动时预设最大订单数,拒绝动态扩容,确保系统在过载时优雅降级而非崩溃;二是恒定工作,通过引入‘保留订单’(reserved)概念,让系统始终处理固定数量的订单,消除动态集合的复杂性,提升性能可预测性。这些技巧虽非万能,却能为关键系统提供宝贵的稳定性保障。
静态分配让你安心:系统可能因内存不足无法启动,但一旦启动,你就能确信它会优雅地处理过载,继续提供服务,同时你从容地扩容机器。
HN 评论区
30- mrkeen
> TigerStyle: 所有内存必须在启动时静态分配。初始化后不得进行任何动态内存分配(或释放再重新分配)。这可以避免显著影响性能的不可预测行为,并避免 use-after-free 问题。
也许维护一个 NULL 订单数组符合“禁止动态分配”这条规定的字面意思,但我并不认为它符合其精神。
难道你只是写了一个 NULL 订单的缓冲区,然后把它借给调用者(也就是所谓的“分配”和“重新分配”)吗?
别人的久经沙场的分配器可能很慢或有 bug,所以你决定作为业务逻辑实现的一部分自己写一个?
- pjmlp
很有趣的是,这么多年过去了,大家似乎总在重新发现 8 位和 16 位家用计算机的编程技巧,我想这主要是过去二十年里“万物皆脚本语言”造成的。
- markus0
我想更多地与那些始终遵循如此严格约束和风格/设计指南的系统打交道,但与此同时,我觉得在大规模构建软件时,现实情况往往是团队最终在那些不考虑程序整体运行模型的子系统中工作。因此,即使局部遵循了最佳实践,整个系统最终也会变得碎片化且效率低下,这使得严格的全球约束感觉反而是一种限制。
- burntcaramel
静态分配的可行性让我感到惊讶。我原本以为它会过于受限,让你不断碰壁。我正在用 Zig/Odin/C 编写 WebAssembly,我能够让 linter 拒绝 `memory.grow` wasm 指令,并在代码中只使用固定大小的缓冲区。
这会让你思考:“好吧,我想要最大图像有多大?”我最终定为 2500 万像素,也就是几百兆字节。由于大多数图像要小得多,我相信在所有主流主机上,内存直到第一次读写时才被分页加载,因此实际内存占用要小得多。
- theokrueger
出于显而易见的原因,静态分配在嵌入式领域是事实上的标准,并且在那里运行得非常好。但在那些内存模型更复杂、完全围绕动态工作负载设计的操作系统中,我不确定要求开发者采用另一种略显复杂的设计模式并施加新的硬性上限,是否比之前减轻了认知负担。
我无法抨击静态分配的功能性和正确性方面,但它有点像朴素的链表:你在着手解决问题之前就应该已经知道你需要它。