用 Rust 写了一个 1MB 不到的“零依赖”图片压缩 CLI,速度比 imagemin 快 9 倍

74 阅读3分钟

1. 背景:为什么又要造轮子?

去年公司 CDN 账单暴涨 37%,追查后发现是图片体积失控:

  • 设计师导出的 2× 图平均 1.3 MB
  • 前端用 imagemin-mozjpeg 压缩一次平均 2.3 s
  • 本地缓存失效后,CI 重新压缩 800+ 张图,直接拖慢流水线 20 min+

imagemin 生态很棒,但有三点实在忍不了:

  1. Node → 80 MB node_modules,CI 每次 npm ci 2 min 起步
  2. 依赖 mozjpeg 动态库,跨平台经常找不到 .so / .dll
  3. WebAssembly 版本单核压缩,8 核 M2 只能吃满 1 核

于是立了个 Flag:

写一个 <1 MB 单文件、零系统依赖、能跑满 8 核、压缩率不低于 mozjpeg 的 CLI,给 CI 用。


2. 技术选型:为什么不是 Go / Zig / C?

语言静态链接体积多线程生态库成熟度
Go2.3 MB(带 runtime)中等(bind 到 libjpeg-turbo)
Zig0.9 MB自己写 JPEG 编解码≈自杀
Rust0.8 MB(strip + lto + panic=abort)jpeg-decoder + turbojpeg-sys

Rust 可以用 jpeg-decoder 纯 Rust 解码,再用 turbojpeg-sys 编码,-C target-cpu=native 开 SIMD,体积也能压到 1 MB 以内,完美符合需求。


3. 架构设计:把“流水线”做成“零拷贝”

┌─── 输入目录 ───┐
│  1. 扫描线程池 │──→ 把文件路径 push 到 crossbeam-channel
└───┬───────────┘
    ↓
┌───┴───────────┐
│  2. 工作线程   │──→ mmap 读文件 → 零拷贝 decode → 编码 → 写盘
└───┬───────────┘
    ↓
┌───┴───────────┐
│  3. 进度条     │──→ indicatif 多进度条,实时显示 MB/s、剩余时间
└───────────────┘

关键优化点:

  1. mmap 读图:4 K 随机读 → 内存映射,减少一次用户态 buffer 拷贝
  2. decode → encode 零中间文件Vec<u8> 复用,减少 40% 内存占用
  3. turbojpeg 的 TJPARAM_OPTIMIZE 开最高:压缩率持平 mozjpeg,但速度 ×3
  4. 线程池大小 = CPU 核心数 × 1.2:防止 I/O 等待时核心打盹

4. 体积瘦身:把 1.2 MB 压到 860 KB 的黑魔法

  1. panic=abort:去掉 unwind 信息,-120 KB
  2. strip + cargo-zigbuild:用 zig 链接器,-60 KB
  3. UPX 3.96 --best --lzma:再 -30%,启动耗时 <3 ms(CI 场景可接受)
  4. feature 裁剪
    • 禁用 default-features = falsejpeg-decoder
    • 只保留 turbojpeg 的 encode 功能,-200 KB

最终体积:
release/laminar 860 KB(Linux x86_64)


5. benchmark:同 800 张 2× 图,M2 Pro 10 核

工具时间平均压缩率内存峰值单文件体积
imagemin-mozjpeg186 s32.7 %1.2 GB80 MB(含 node)
laminar(本文)21 s32.4 %350 MB0.86 MB
提速×8.9持平-70 %-98.9 %

6. 使用方式:一条命令搞定

# 安装
curl -sSL https://github.com/yourname/laminar/releases/latest/download/laminar-linux-amd64 \
  -o /usr/local/bin/laminar && chmod +x $_

# 压缩
laminar ./static/images -q 85 -o ./static/images_optimized

# CI 集成
- name: Optimize images
  run: |
    laminar ./public/images --quality 80 --ext .jpg

支持参数:

  • -q, --quality 1-100
  • --ext 输出后缀(默认覆盖)
  • --threads 手动指定线程数
  • --dry-run 只统计不写入

7. 开源地址 & Roadmap

GitHub: github.com/yourname/la…
欢迎 ⭐ / Issue / PR!

下一步:

  • 支持 AVIF 编码(dav1d-sys)
  • 增量压缩(基于 mtime + SHA-1)
  • GitHub Action 官方镜像

8. 小结:把“性能”做成“成本”

一张图 1.3 MB → 420 KB,每月 3.2 TB 流量,直接省下 470 元/月 的 CDN 费用;
CI 从 20 min 降到 2 min,开发者每天少等 1 h,按 100 元/h 人效,每年省 3.6 万
而整个 CLI 只占 860 KB 磁盘空间,真正做到“小而美”。

如果你也在被 imagemin 的速度和体积折磨,不妨试一下 laminar,欢迎一起把轮子滚得更圆!