科技·via

在 2500 亿条全局缓存的尺度下,结构体里每多留一个无用字段,代价是 15000 GB 物理内存

在 2500 亿条全局缓存的尺度下,结构体里每多留一个无用字段,代价是 15000 GB 物理内存。 每条缓存只要浪费一个字节,乘以全网基数,就是 250 GB 内存凭空蒸发。 现代编程语言总在强调灵活、动态、可扩展。 但当海量数据一旦写入就永远不可变时,那些为了防御未知扩容而预留的抽象,到底在暗中吞噬多少硬件账单? Cloudflare 负责承载 1.1.1.1 的 DNS 解析引擎 Big Pineapple,在全球同时维持着 2500 亿条缓存。 工程师在底层 Rust 内存布局上连砍 5 刀,把单条缓存从 953 字节压到 420 字节。 全网直接抠出 100 TB 内存,相当于 130 台顶配 Gen 13 服务器的物理内存总和。 更反直觉的是:内存砍掉 56%,写入吞吐反而暴涨 43%,读取延迟下降 19%。 这台让事件得以发生的机器,叫不可变数据的动态税。 第一刀,砍掉容量字段。 开发习惯随手使用的 Vec 和 String,在底层都由 3 个字段构成:指针、长度、容量,各占 8 字节。 只要数据存入缓存,生命周期内就绝不再追加。 那个用来防扩容溢出的容量字段完全是摆设,加上堆上过度预留的闲置空间,纯属白白占座。 换成只含指针和长度的不可变切片 Box,单条缓存 8 个字段直接省下 64 字节。 仅这一步,全网立刻抹掉 15 TB 内存。 第二刀,消除多余指针。 一条 DNS 响应包含应答、权威、附加三部分数据。 传统设计会建 3 个独立列表,各背 16 字节的指针和长度。 改造成一个连续大列表,每个分区的切分点改用 2 字节的 u16 偏移量定位。 干掉 2 个独立列表,净省 28 字节,同时消除多余的内存对齐填充。 第三刀,拆解枚举的对齐膨胀。 Rust 的枚举是标签联合体,整个结构体的大小,必须硬性对齐体积最大的那个变体。 一个极其罕见的 NAPTR 记录因为字段繁多占了 136 字节,直接把整个记录枚举强行垫到 144 字节。 而占全球 80% 以上流量的普通 A 记录只有 4 字节,AAAA 记录只有 16 字节。 这意味着每一次最常规的解析,都要替那个几乎遇不到的冷门记录,平白背上 120 多字节的空白填充。 把超大变体单独装箱丢到堆上,让核心枚举瞬间缩回正常体型。 第四刀,剥离冗余的所有者域名。 绝大多数解析记录的域名,和最初发起的查询键名完全一致。 把原本每个记录都单独存一份的域名改成 Option,一致时设为 None,读取时直接复用现成键名,零堆分配。 只有遇到 CNAME 这类跳转、域名发生变化时,才在堆上分配存储。 第五刀,回归网络线格式。 堆上分散的独立分配,会让内存分配器 jemalloc 产生尺寸对齐浪费,更会把数据打散在内存各处。 最终把记录数据直接打包成紧凑的原生二进制线格式,通过全局复用的缓冲池写入。 这不仅消除了内存碎片,更带来质的飞跃: 由于主流记录已经是二进制格式,组装 DNS 响应时直接整块内存复制,跳过了逐字段序列化的 CPU 开销。 计算机科学里有一条常识:时间换空间,空间换时间。 但在现代硬件架构下,这条规律正在被改写。 CPU 的计算速度极快,真正的瓶颈永远卡在数据在内存与核心之间搬运的延迟。 当离散的指针被消除、多余的填充被剔除,数据紧凑地聚拢在连续内存中,CPU 缓存一行就能装下整条记录。 减少内存占用的过程,恰恰消灭了 CPU 跨越堆空间去寻址的开销。 最顶级的系统性能优化,往往不是去换更快的芯片。 是在数据不可变的那一刻,果断剥掉现代编程语言强加给你的所有动态幻觉。

相关 / RELATED

JSON

导出 / EXPORT

订阅 · SUBSCRIBE

每周一封信号简报,重大进展可选即时推送。

不追踪邮件打开与邮件链接点击,一键退订。 或用 RSS · 详情