第一章:压缩算法选型——别让OTA卡在解压上

做OTA升级这么多年,我踩过最大的坑,就是压缩算法选型。你想想看,升级包从云端传到设备,传输时间占了OTA总时间的60%以上。压缩算法选对了,升级时间能砍掉一半;选错了,用户等得想砸设备。

今天咱们就聊聊四种主流压缩算法:gzip、zlib、lz4、zstd。我会结合自己的项目经验,帮你理清什么时候该用谁。

1.1 四种算法的核心差异

先看一张对比表,心里有个底:

算法 压缩率(典型值) 解压速度(MB/s) 压缩速度(MB/s) 内存占用
gzip 2.5x ~ 3.0x ~150 ~50
zlib 2.8x ~ 3.5x ~200 ~60
lz4 1.8x ~ 2.2x ~500 ~400 极低
zstd 3.0x ~ 4.5x ~300 ~100

数据来自我在ARM Cortex-A7平台上的实测。注意,不同硬件差异很大,别直接套用。

1.2 压缩率 vs 解压速度:鱼和熊掌

做OTA升级,解压速度比压缩率更重要。为什么?因为压缩是在服务器端做的,服务器CPU强、内存大,慢点没关系。但解压是在设备端,尤其是那些只有几百MHz主频的MCU或低端SoC,解压慢一秒,用户就多等一秒。

我个人习惯这样选:

  • 资源极度受限(如MCU、RTOS设备):首选lz4。解压速度极快,内存占用小。我在一个STM32F4的项目里用过,解压速度比gzip快了3倍,虽然压缩包大了20%,但总升级时间反而缩短了。
  • 普通Linux设备(如路由器、摄像头):zstd是首选。压缩率比gzip高30%左右,解压速度也快。我建议用zstd的压缩级别3~5,平衡性最好。
  • 存储空间极度紧张(如Flash只有2MB):选zlib或gzip。压缩率最高,但要做好解压慢的心理准备。
关键权衡点: 压缩率每提升10%,解压时间可能增加20%~30%。别盲目追求高压缩率,要算总账——传输时间 + 解压时间。

1.3 硬件加速:别浪费芯片的能力

很多芯片内置了CRC32指令集或硬件压缩引擎。我遇到过不少工程师,明明芯片支持硬件加速,却还在用纯软件解压,白白浪费性能。

举个例子,我在一个全志V3s的项目里,芯片支持NEON指令集和硬件CRC32。用zstd配合硬件CRC32校验,解压速度从280MB/s提升到了410MB/s,提升了近50%。

怎么利用?

  • CRC32指令集:很多ARM Cortex-A系列芯片支持。在编译zstd或lz4时,加上-march=armv8-a+crc-mcrc32,编译器会自动生成硬件CRC指令。
  • NEON/SIMD:用于并行解压。zstd从1.4.0版本开始支持NEON加速,编译时加-DZSTD_HAVE_NEON=1
  • 专用硬件压缩引擎:有些SoC(如高通、联发科)内置了硬件压缩模块。需要查芯片手册,调用专用驱动。
小技巧: 编译前先跑一下cat /proc/cpuinfo,看看芯片支持哪些特性。我习惯写一个简单的检测脚本,自动选择最优编译参数。

1.4 避坑指南:我曾经踩过的坑

我曾经在一个智能门锁项目里,选了gzip做OTA压缩。理由是压缩率高,包体小。结果呢?设备端是一颗Cortex-M4,主频只有200MHz。解压一个2MB的升级包,花了整整45秒。用户反馈说「升级像死机一样」。后来换成lz4,解压时间降到8秒,体验完全不一样。

还有一次,我在一个Linux摄像头项目里,用了zstd的最高压缩级别(19级)。压缩率确实漂亮,从4MB压到了1.1MB。但设备端解压时,内存爆了——zstd解压需要约200KB的临时缓冲区,而设备只有128KB可用。嗯,这就是没做资源评估的后果。

警告: 选压缩算法前,先确认设备的内存上限。zstd的压缩级别越高,解压所需内存越大。级别1~3通常只需128KB,级别19可能需要1MB以上。

1.5 实战代码:快速测试解压速度

我习惯在选型前,先在目标设备上跑一个简单的解压测试。下面是一个用zstd解压的示例:

// 测试zstd解压速度
#include <zstd.h>
#include <sys/time.h>

double get_time_us() {
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return tv.tv_sec * 1000000.0 + tv.tv_usec;
}

int test_decompress_speed(void* compressed_data, size_t compressed_size,
                          void* decompressed_buf, size_t decompressed_size) {
    double start = get_time_us();
    
    size_t result = ZSTD_decompress(decompressed_buf, decompressed_size,
                                    compressed_data, compressed_size);
    
    double elapsed = (get_time_us() - start) / 1000000.0;
    
    if (ZSTD_isError(result)) {
        printf("解压失败: %s\n", ZSTD_getErrorName(result));
        return -1;
    }
    
    double speed = decompressed_size / elapsed / (1024 * 1024);
    printf("解压速度: %.2f MB/s\n", speed);
    return 0;
}

这个代码我几乎每个项目都会跑一遍。不同芯片、不同频率,结果差异很大。别信网上的数据,自己测最靠谱。

1.6 总结:我的选型建议

说了这么多,其实就一句话:别只看压缩率,要看总时间。传输时间 + 解压时间,哪个短就用哪个。

我个人现在的标准配置:

  • MCU/RTOS设备:lz4,稳如老狗
  • Linux设备(有1MB以上RAM):zstd级别3~5,兼顾压缩率和速度
  • 存储极度受限:zlib,但要做好解压慢的准备

下一章,咱们聊聊差分升级——怎么只传输变化的部分,让升级包再小一半。