明明只是把 10.12 存进数据库,却要装进 64 个比特的科学计数法口袋里,同时还得为每一次查询和解压付出计算开销——浮点数的压缩,为什么总是让人在速度和大小之间左右为难?

传统浮点压缩的一个核心矛盾是:越想把数据压得紧实,编解码的计算量就越像洪水般涌来;反过来,追求极致速度的算法,又常常在压缩比上“放水”,让存储和传输成本居高不下。这背后的根源,正是 IEEE 754 格式本身。标准浮点数通过符号位、指数和尾数精密地表达数值,但现实世界的数据流里,大量值根本不是“纯血”二进制小数,而是披着浮点外衣的十进制常客。直接把这样一个混杂的组合交给通用压缩器,就好像把财务报表和天体物理仿真结果揉成一团再硬塞进同一个模型,效率难免低下。

打开网易新闻 查看精彩图片

这种两难境地,在工业界和学术界催生了一大批解决方案,有的偏重利用数值的局部顺序性,有的依靠字典替换,有的则混合上差分和熵编码。但在广泛的实际负载面前,它们总是顾此失彼——要么在整数模式上表现出色却在随机尾数区域崩溃,要么解压时消耗了过多的 CPU 周期,抵消了压缩节省的存储带宽,最终对端到端延迟毫无帮助甚至倒退。

打开网易新闻 查看精彩图片

ACM SIGMOD 2024 上,CWI 的 Azim Afroozeh、Leonardo Kuffó 和 Peter Boncz 联合提出了一种名为 ALP(Adaptive Lossless Floating-Point Compression)的自适应无损浮点压缩算法。该工作的核心主张是:与其用一个统一的规则去套所有浮点数,不如直接识别并利用真实数据中两种截然不同的存在模式,并为每种模式匹配一套极度优化、且 SIMD 友好的编码流水线。结果是,在同样无损的前提下,ALP 在压缩速度、压缩比和解压速度这三个几乎所有压缩算法都难以同时拔尖的维度上,全部超越了现有的主流方案。这个判断并非宣传口号,而是经过了严格的基准测试和 ACM 官方可复现性评审,相关代码、数据集和测试脚本已在 GitHub 开源。

要理解 ALP 为什么能做到这一点,需要先回到浮点数的实际来源。在很多数据集里,数值最初是以十进制形式被人或业务系统记录下来的,比如价格 10.12、温度 36.6、或者汇率 0.8725。这些数值进入计算机后被转成二进制浮点数,形式上变成了类似 1.012 × 10^1 × (某些二进制尾数) 的格局,但其背后的真实语义依旧是“小数点后最多几位”的十进制小数。ALP 抓住这一特征,在算法前端就对这些值进行检测,一旦确认为十进制定点语义,便直接将其乘以 10 的适当次幂——对于 10.12 就是乘以 100 得到 1012——干净利落地映射为一个精确的整数。这里没有截断误差,也不再需要处理循环尾数,一举把整个数值从 64 位浮点的“重包装”里解放出来,变成一个可以在整数组件里高效处理的对象。

得到整数序列之后,ALP 没有简单套用现成的通用整数压缩器,而是引入了一个为加速而生的 FastLanes 变体 Frame-of-Reference 编码。Frame-of-Reference(FoR)的基本思想是:当一组数值挤在一个相对较小的范围内时,不必存储每个完整的绝对数,只需记录一个共同的基值,然后每个数只保存它相对于这个基的偏移量。这样一来,原本需要 64 位表示的整数,偏移量可能只需要为数不多的几个比特。FastLanes 的改造更进一步,它把这段逻辑大量映射到现代 CPU 的 SIMD 指令单元上,一次操作便能并行处理多个偏移量的打包或解包,极大降低了编解码的延迟。ALP 在此之上做的,就是把前一步导出的整数流无缝喂进这条 SIMD 管道,让十进制模式的压缩和解压过程几乎可以全速在向量化执行中完成。

如果世界上所有浮点数都长成带小数点的样子,那算法到这里就可以结束了。但现实中还有另一类完全不同的存在:充满着高精度要求的物理常数、迭代计算残差、积分中间值,或者随机模拟产生的近乎白噪声的尾数。这些数值的二进制表示里,低位哪怕只有一丁点改变,也可能在后续运算中引发蝴蝶效应。对它们强行应用前述的整数映射,不是丢失精度,就是根本不存在一个有限的十进制等价格式。ALP 的处理策略在这里做了一个极其清醒的设计让步:既然无法无损压缩整个 64 位,那就只去啃掉左半部分那些有结构、有冗余的位,而把剩余部分的原始比特原封不动地保留。

具体来说,当一个浮点数被识别为高精度模式时,ALP 会将其底层位表示切分成两个区段。左段通常包含符号、指数,以及尾数的高位部分,这些比特在连续的数值中往往具有高度的重复性或可预测性,比如同一量级的数据指数完全一样,或者尾数前几位的重复模式非常稳定。针对这一段,ALP 使用 FastLanes 字典编码进行压缩——把出现过的不同左段模式收集起来建成一个小型字典,然后在数据流里用字典索引去替换原本较宽的比特串。这个操作在概念上类似于文本压缩里用“令牌”代替重复词组,只不过这里是在寄存器级别的位串上进行。同样地,FastLanes 层的 SIMD 优化让字典查找和替换、以及编码前后的重排都能借助向量指令大幅提速,避免了传统字典方法在高频数据流中可能成为瓶颈的串行查表开销。

右半部分则是尾数的低位比特——它们几乎就是随机的,过去数十年的压缩研究反复证明,纯粹的随机比特串没有可压缩的信息冗余。强行用通用算法去压,不仅换不来可观的体积缩减,反而会引入极重的计算税,在解压时还得逐位还原,拖累一切下游计算。因此 ALP 对这些比特完全不处理,直接把它们作为负载透明地附加在压缩后的左段数据后面。这种“有舍有得”的思想,使得算法不会为追求纸面上的压缩比而牺牲掉实际性能,也不会因为局部不可压缩的噪声尾数而拖累整条流水线的吞吐率。

打开网易新闻 查看精彩图片

两种模式自适应切换的关键,在于 ALP 前端的决策逻辑。尽管论文没有详细展开这个分类器本身的具体电路,但结合算法描述可以明确,其判断依据并非来自某个外部的元数据标记,而是完全基于数值本身的数学特征——即它能否通过乘以 10 的幂无损映射为整数。这个检查在实现上可以利用浮点数的内部表示,只需若干次乘法与舍入校验,计算开销极低,完全可以在扫描数据流的同时即时完成,既不会造成额外的一次数据遍历,也不会为后续的压缩管道增加不可控的分支。经过这一层轻量级判断后,每一个浮点数都被打上“十进制”或“高精度”标签,随后被送到对应的编码模块中,整个过程像生产线一样连贯。

从批判性视角复盘以上设计,必须直面的一个质疑是:真实世界的数据集真的会整齐地划成这两种模式吗?若是数据同时混杂了近似十进制的高精度值与几乎十进制的值,边界又在哪里?作者的解法是,ALP 并不追求一个理论上的完美分类,而是让“能整数映射的值”自然地被十进制管道理顺,剩下的全部落入高精度管线。这种非对称的设计实际上把判断责任从算法转移给了数据特性本身——只要数据集中存在大量有限小数,就能获得可观的压缩增益;如果数据集充满了不可约简的高精度值,则 ALP 自动退化为一种高效的半字典压缩器,而不会因为强行套用不合适的模型而反倒出现膨胀或性能崩塌。换句话说,这个算法在最坏情况下的行为是可控的,而在常见场景下可以跑出远优于单模式压缩器的表现。

另一个值得审视的维度是与现代硬件的结合深度。ALP 并非仅仅在已有压缩框架上打几个 SIMD 补丁,而是从编码原语的选择(FoR、字典)、数据排布的方式到访存模式,都围绕着向量化执行重新思考。例如,在使用字典编码时,数据组织会刻意对齐到 64 字节的缓存行边界,并且字典索引的宽度会根据实际条目数动态选择,确保从内存中抓取压缩段时能够一次加载足够多的整数偏移或字典索引,填满 AVX-512 或类似宽向量寄存器,达到最高的 IPC。这种软硬件协同精细调控,在传统压缩库中并不常见,却恰恰是 ALP 能够在解压速度上反超众多以速度著称的算法的关键所在。

当然任何技术路线都有其前置假设。ALP 的设计强烈依赖于两个经验认知:第一,大量现实浮点数据源自十进制输入或存储,而不是纯二进制计算产物;第二,高精度值的低位尾数通常没有压缩可能。如果未来出现一种新型数据格式,或者某些特定领域的仿真数据完全以均匀分布的低频随机尾数为主,且鲜少十进制来源,那么 ALP 的优势可能被削弱。但对于目前可公开获取的科学计算日志、金融交易记录、物联网传感器读数和时序数据库存储,这些假设看来是相当稳妥的。更关键的是,作者团队不仅公布了完整的实验环境和脚本,还通过了 ACM SIGMOD 的可复现性与可用性评估,使得外部研究者可以自行在任意数据集上重新验证上述假设的边界,这在数据库压缩研究史上也是一次透明度上的进步。

综合来看,ALP 的策略并不是靠某个单点突破一举封神,而是通过把浮点数按语义拆解,然后为每一类找到与其微观结构高度匹配、且能在现代 CPU 上全速奔跑的编码工具,最终