1. 背景:为什么又要造轮子?
去年公司 CDN 账单暴涨 37%,追查后发现是图片体积失控:
- 设计师导出的 2× 图平均 1.3 MB
- 前端用 imagemin-mozjpeg 压缩一次平均 2.3 s
- 本地缓存失效后,CI 重新压缩 800+ 张图,直接拖慢流水线 20 min+
imagemin 生态很棒,但有三点实在忍不了:
- Node → 80 MB node_modules,CI 每次
npm ci2 min 起步 - 依赖 mozjpeg 动态库,跨平台经常找不到
.so/.dll - WebAssembly 版本单核压缩,8 核 M2 只能吃满 1 核
于是立了个 Flag:
写一个 <1 MB 单文件、零系统依赖、能跑满 8 核、压缩率不低于 mozjpeg 的 CLI,给 CI 用。
2. 技术选型:为什么不是 Go / Zig / C?
| 语言 | 静态链接体积 | 多线程 | 生态库成熟度 |
|---|---|---|---|
| Go | 2.3 MB(带 runtime) | ✅ | 中等(bind 到 libjpeg-turbo) |
| Zig | 0.9 MB | ✅ | 自己写 JPEG 编解码≈自杀 |
| Rust | 0.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、剩余时间
└───────────────┘
关键优化点:
- mmap 读图:4 K 随机读 → 内存映射,减少一次用户态 buffer 拷贝
- decode → encode 零中间文件:
Vec<u8>复用,减少 40% 内存占用 - turbojpeg 的
TJPARAM_OPTIMIZE开最高:压缩率持平 mozjpeg,但速度 ×3 - 线程池大小 = CPU 核心数 × 1.2:防止 I/O 等待时核心打盹
4. 体积瘦身:把 1.2 MB 压到 860 KB 的黑魔法
- panic=abort:去掉 unwind 信息,-120 KB
- strip +
cargo-zigbuild:用 zig 链接器,-60 KB - UPX 3.96
--best --lzma:再 -30%,启动耗时 <3 ms(CI 场景可接受) - feature 裁剪:
- 禁用
default-features = false对jpeg-decoder - 只保留
turbojpeg的 encode 功能,-200 KB
- 禁用
最终体积:
release/laminar 860 KB(Linux x86_64)
5. benchmark:同 800 张 2× 图,M2 Pro 10 核
| 工具 | 时间 | 平均压缩率 | 内存峰值 | 单文件体积 |
|---|---|---|---|---|
| imagemin-mozjpeg | 186 s | 32.7 % | 1.2 GB | 80 MB(含 node) |
| laminar(本文) | 21 s | 32.4 % | 350 MB | 0.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, --quality1-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,欢迎一起把轮子滚得更圆!