资讯动态

工业时序数据轻量压缩:差分编码与变长整数实战

发布时间:2026/10/18 20:33:02 来源:尧图企业网站定制
前阵子被车间里一台设备日志的真实情况整破防了。一台很普通的采集网关一小时产生将近两百兆原始记录也就是一组一组带时间戳的浮点数温度、压力、振动幅度、电流、电压。这些数据单个看都不大四个字节一个 float但架不住采样频率高、通道数量多攒一天的体量就非常可观。当时的想法很简单能不能不换硬件、不加带宽用一点代码把存储和传输的压力降下来。于是就有了这个每周编程小例子——实现一套工业数据轻量压缩。它不是什么高深算法也不是要跟专业压缩库比拼极限压缩率而是解决一个非常现实的问题在设备端、网关上用有限的 CPU 和内存把数据变轻一点再丢给后端。这个方向看起来简单真正做起来会发现好几个地方都需要取舍。下面的内容就是我从踩坑到调参的完整记录包括方案选择、核心实现、实测数据和几个容易忽略的细节适合正在做工业数据采集、边缘网关、日志归档或者纯粹对嵌入式数据压缩感兴趣的朋友。1. 这个每周小例子要解决的三个真实问题1.1 工业数据的真实长相工业现场的数据不像图片、视频那样动辄几 MB也不像数据库里的字符串那样长短不一。它是非常典型的时序数据每一个通道的数值固定在某个物理范围内波动。比如某个温度通道在正常运行状态下基本在 60 到 80 摄氏度之间上下浮动可能只有零点几某个压力通道则在 1.0 到 1.2 兆帕之间小幅度变化。这种数据的特点是相邻两个采集点的差值很小绝对值却占满了四个字节甚至八个字节。如果直接把数据打包成 JSON 或者 CSV 发送光文本格式化就要吃掉大量空间。“4.2000001”这个字符串十一个字符实际上数据本身只需要一个 float。最直接的问题就在这里工业数据在格式层面极其“浪费”。等设备数量上百、采集频率到每秒一次一天积攒下来的冗余就是几百 GB 级别。这时候不改动任何采集逻辑只需要在输出前做一次轻量转换就能把体积打下来。1.2 压缩目标不能只看压缩率先明确一个容易被忽略的事情轻量压缩的三个目标里压缩率只占其中之一。后面还有两个更重要的一是 CPU 占用要低二是内存占用要有界。设备端的控制器和网关往往不是高性能服务器很多用的还是主频几百兆的嵌入式处理器内存可能只有几十 MB。如果压缩算法需要一次性把几百 KB 的数据读入内存再开个大缓冲区网关就会非常吃力。通用压缩库在这种场景下经常表现不佳并不是算法不够好而是它默认的语义和目标跟工业场景不匹配。工业场景的数据是持续生产的要求边采边压边传延迟可控而通用压缩往往面向文件批量处理天然就是“一次性读入一次性输出”的思路。所以这个例子里我给自己定的指标是单包最大等待时间不超过一秒内存占用控制在几十 KB 级别压缩率不做极限追求能有明显体积下降就行。先定目标再选方案比先选方案再套目标要靠谱得多。1.3 为什么强调“轻量”“轻量”这个词意味着可以跑在很多不太起眼的地方一个工控机上的后台服务、一块边缘计算板卡里的数据采集程序、一个手持巡检仪上的记录模块。我见过很多项目为了压缩数据引进来一套重量级方案最后发现部署环境根本扛不住还要再拆。轻量压缩最理想的形态是一个独立的小模块输入字节流输出字节流不依赖外部大库方便在 C、C、Python、Rust 各种环境里复制。做一个编程小例子重点不是发明新算法而是把几种简单成熟的技术串成一条管线。管线里的每一段都很容易测试单独拎出来也能复用。这样即使压缩效果不理想排查起来也比较舒服不会像黑盒一样让人无从下手。2. 方案取舍那些看起来很美的算法为什么不能直接用2.1 通用压缩库在网关场景的下场一开始我确实考虑过直接用现成的通用压缩库。它在 PC 上处理文件表现确实不错压缩率能到 3:1 甚至更高但放在实时数据链路上就出现了几个问题。第一个是延迟不可控它通常需要积累一段完整数据块之后才开始压缩数据不够大时压缩效率很低第二个是内存占用随数据块大小线性增长传到 64KB 以上时嵌入式环境就开始紧张第三个是它对“微弱相关性”的数据不够友好。工业时序数据实际上是非常强相关的但通用压缩库并不知道这一层。它看到的是一个一个的浮点数只能靠字节层面的重复来压缩。而如果我们先做一次差分把 60.123、60.456、60.789 变成 0、0.333、0.333 这种小数差序列再用整数手段处理同样的数据压缩率可以提升不少。与其把希望寄托在通用算法的通用性上不如先做领域预处理。2.2 工业数据可压缩性是从哪里来的想清楚一个数据能不能压核心是看它有多少冗余。工业时序数据的冗余主要来自三方面时间戳的高位重复、数值的连续平稳波动、以及固定字段的重复出现。时间戳这块特别典型。每秒采一次一分钟内的秒级时间戳低位在变但前面的年月日小时分钟几乎不变。如果每个采样点都完整记一个 Unix 时间戳就是巨大的浪费更聪明的做法是只记首时间戳和后续增量增量大概率很小。数值部分类似相邻采样的差值往往只占原始位宽的一小部分。固定字段则更明显比如设备编号、通道名称、质量状态标志很多行都一样。这三类冗余分别对应差分编码、变长整数、字典映射三种手段。把这几样组合起来就足够解决绝大多数工业数据压缩场景。2.3 我给这套方案定的四段式管线最后落地的方案是四段式管线每一段都有明确职责第一段切块与数据规范化把原始记录转换成对齐的数值数组便于后续处理第二段差分编码把绝对数值变成差值序列重点处理时间戳和测量值第三段位打包和变长整数把差值序列进一步压成尽可能少的比特位第四段字典去重与头信息压缩处理那些重复度极高的枚举字段和设备标识字段。这个结构的优点是每一段都可以单独测试。测试的时候我喜欢先做反向验证先跑一个随机噪声数据如果压缩结果几乎不缩小说明管线没有误伤正常波动再跑一个恒值数据如果压缩结果没有明显变小那是头信息写得太浪费。这种测试方法比单纯看最终压缩率更可靠。3. 实操用 Python 和 C 风格的思路落地压缩管线3.1 数据准备先统一成数值数组在动手写压缩之前得先把输入数据整理成一个可以预测的形态。工业现场的数据来源很杂有的从 MODBUS 寄存器读出来有的从 OPC 订阅拿过来有的干脆是设备自己生成的日志文本。我不建议直接压缩原始文本因为文本里掺杂了逗号、冒号、换行这些格式字符压缩手段会被格式噪声牵扯。我一般会先做一个标准化步骤把每条记录拆成通道 ID、时间戳、测量值、质量标志四个字段全部转成数值。测量值统一成 IEEE 754 的 float 或 double时间戳统一成单调递增的整数。这个步骤很简单却能大幅提升后续压缩的稳定性。标准化之后的流水线不再关心设备型号只要输入是同样结构的数组压缩代码完全可以复用。这一步也是我最想建议大家认真做的地方很多压缩效果不好的项目最后查原因都出在输入数据格式不统一。3.2 差分编码和时间戳处理标准化之后的下一步是差分。假设原始时间戳是 1717680000、1717680001、1717680002直接记录这三个 int32 需要 12 字节如果先记录起始时间戳 1717680000后面记录增量 1、1就只需要很少的比特位。测量值也要差分但是浮点数差分不能直接按 float 位操作否则会因为指数位产生奇怪的结果。比较稳妥的做法是先把浮点数转换成整数表示再差分。工业场景里很多测量值的小数位数是固定的比如温度保留两位小数、压力保留三位小数。既然如此可以直接把浮点数乘上一个比例因子转成整数比如 60.123 转成 60123然后对这个整数做差分。这样既能避免浮点尾数产生不必要的噪声也能让差分的数值范围变得可控。比例因子需要记录在头信息里后端解压时再除回去。3.3 位打包和变长整数把“看起来一个数字”变成“必要的比特”差分之后的序列有一个特征大多数差值的绝对值都在很小的范围内偶尔会有大的跳变。这种情况非常适合用变长整数编码。一种常见做法是把整数映射成无符号表示然后用字节数来标记范围。这里我给出一个简单的实现思路代码不长却非常实用def uvarint_encode(value): buf bytearray() while value 0x80: buf.append((value 0x7F) | 0x80) value 7 buf.append(value) return bytes(buf) def uvarint_decode(buf, offset): result 0 shift 0 while True: b buf[offset] offset 1 result | (b 0x7F) shift if b 0x80: break shift 7 return result, offset这段代码是标准的可变长整数编码。数字越小占用的字节越少跟工业数据差值的分布完美契合。对于有符号数值还需要先做一个映射把正负数映射到无符号区间。常见做法是 ZigZag 映射0 映射到 0-1 映射到 11 映射到 2-2 映射到 3。这样做的好处是小的负数和小的正数都能用很小的变长整数表示。3.4 字典去重与头信息设计除了数值和时间戳工业记录里还有很多重复度高的字段。比如设备状态位正常时都是“运行中”质量标志位大部分时候都是“Good”通道名称几十条记录对应同一个通道。对这些字段做字典去重比直接压缩更高效。我的做法是维护一个递增的字典表第一次遇到某个字符串时写一个“新增字典项”标记后面遇到相同字符串就只写一个字典索引。字典表放在压缩文件头或数据包头部解压时先读字典再还原字段。这里要注意一个问题字典不能无限膨胀否则整个头信息会比正文还大。实践中我会限制字典项数量超过上限后就不再新增只使用已有索引实在遇到新值就退回到原始字符串写法。头信息设计要尽量紧凑。不要每个小段都重复写设备 ID 和采样频率把这些公共信息放在整个数据块头部正文里只保留变化部分。我见过不少人把头信息写得特别宽比如用四个字节存储采样周期实际上采样周期就那几种取值一个字节足够。4. 压缩率实测记录从 55% 到 71% 的调整过程4.1 初始版本的真实效果第一次跑完整管线我用的样本是某条产线采集网关里一段持续两小时的数据包括两个温度通道、一个压力通道、一个振动有效值通道采样间隔是一秒总共 7200 条记录。原始数据按标准二进制格式保存大约 1.6MB转换成 CSV 文本则更大。初始版本完成差分和变长整数编码后体积大约 720KB压缩率只有 55%。说实话这个结果不算好看。问题出在差分之后的整数序列里偶尔会出现一个特别大的跳变值。比如阀门动作瞬间压力从 1.02 猛增到 1.85对应差分值在整数表示下是很大的一个数。变长整数编码虽然能处理大数但一旦大数出现次数频繁节省的比特就有限了。4.2 参数调优分块阈值和剪枝策略第一个调整是分块。把数据按行为段切割每个块内重新计算基准值而不是全程使用一个基准。这样阀门动作产生的跳变只会影响所在块不会污染后续所有数据。分块长度我最后定在 256 条记录折中考虑了两方面块太短则头信息开销占比升高块太长则基准值容易跟不上下一个工况状态。第二个调整是差值剪枝。对某些通道当差分绝对值超过一定阈值时不再走差分编码而是记录一个标记位并写完整绝对值。这个阈值怎么定我使用的方法是统计差值的分布把 99% 的差分覆盖到最小位宽内剩下的 1% 走异常分支。合理设置阈值之后同样体积能降到 520KB 左右压缩率到了 67.5%。4.3 字节序和序列化方式带来的隐蔽开销第三轮调优时发现了另一个惊喜字节序影响很大。不同语言和设备采集平台的默认字节序不一样。原本在小端机器上压缩的数据换到大端环境中一解压全是乱的。我后来统一在压缩头里写一个标志字节并在序列化多字节数据时统一采用小端序。排序上也有讲究如果把同一通道连续排列差分效果更好如果按时间交错排列差分后的值就会来回跳。这一轮下来同样的 7200 条记录被压到约 460KB压缩率 71.2%。解码后的数据跟原始数据逐字节对比完全一致。整个压缩过程发生在单核 800MHz 处理器上单块耗时不到 15 毫秒内存占用稳定在 32KB 左右。这个指标已经足够满足现场“每秒一组数据”的实时处理需求。5. 关键细节与坑调试时容易忽略的地方5.1 时间戳的“几乎连续”陷阱第一类坑是时间戳断点。很多设备在重启、断网、丢包之后时间戳会跳变。如果压缩端只做差分一个断点会让后续所有差分值变成巨大的跳变压缩率急剧恶化。解决方法是设置一个最大时间间隔阈值相邻两条记录的时间差超过这个阈值时就主动写入一个完整的绝对时间戳并重置差分基准。这个处理只增加少量字节却能把数据块内部的连续性重新拉回来。5.2 浮点比较不能直接用精确相等第二类坑出现在校验和测试阶段。解压之后做数据对比如果用 float 精确相等进行比较会偶尔失败。原因是浮点数在转换乘比例因子和逆转换过程中可能会产生极小误差。正确的做法是定义一个误差容限比如 |解压值 - 原始值| 0.0001 就算通过。这个误差在最后还原数值时不感知但能节省不少头疼的时间。5.3 压缩算法不会替你清理脏数据第三类坑是我最想强调的压缩不会清理脏数据只会忠实保存脏数据。如果采集端偶尔读到 0xFFFF 代表传感器异常或者某个通道在断电时可能产生毛刺这些值经过差分编码后反而会造成巨大的体积膨胀。更麻烦的是如果压完的数据被后端直接用于展示工程师可能因为自动过滤掉的异常值而忽略设备故障。因此我强烈建议在标准化阶段就把异常值字段和质量标志位分开编码异常样本单独标记不要跟正常测量值混在同一个差分流里。5.4 性能和压缩率的平衡像削铅笔第四类坑是性能测试方法。不要只看平均耗时还要看最差耗时。工业场景偶尔会出现一个大块数据塞进来如果压缩模块在最差情况下卡住几百毫秒就会影响后续采集任务的实时性。我的习惯是给压缩函数设置一个硬性的处理上限超时就把当前块切成两个子块调度到下一次处理而不是在一个块上死磕。6. 后续还能怎么扩展这套轻量压缩思路目前已经放在一个小型采集模块里跑了挺长时间稳定性和收益都达到了当初设定。如果你也想在自己项目里尝试我建议从最基础的差分加变长整数开始先不要急着加复杂字典和异常处理功能。跑通一条最小可用链路之后观察真实数据的分布特征再做针对性优化。如果后续数据量继续增长还可以考虑流式压缩和分块索引。流式压缩的好处是延迟更低数据产生的瞬间就被压缩发送不占用存储分块索引则能让后端在不解压全部数据的情况下快速定位某段时间区间的数据。再往后走如果需要对振动信号这类高频数据进行压缩可能还需要引入频域变换但那就已经超出“轻量”这个范畴了。我个人在实际操作中体会到的最重要的一点是轻量压缩的收益看似来自算法实际上更多来自数据整理。把输入标准化了把时间戳对齐了把异常值单独分开了那些通用技巧自然就能发挥出效果。这也解释了为什么很多同学直接套变长整数编码却得不到理想压缩率因为他们漏掉了前面的数据理解环节。希望这个每周小例子能帮你少走一段弯路也欢迎你从自己手头的一小段数据开始试一下。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑