性能基准
GetWebP CLI 在各种工作负载下的性能表现及其原因。
性能基准
GetWebP CLI 在各种工作负载下的性能表现及其原因。
架构概述#
理解 GetWebP 的性能特征需要理解其处理管道。
WASM 编解码器管道#
GetWebP 使用 jSquash WASM 编解码器,编译自支持 Google Squoosh 的相同 C/C++ 库:
| 编解码器 | 来源 | 用途 |
|---|---|---|
@jsquash/jpeg | MozJPEG | JPEG 解码 |
@jsquash/png | Squoosh PNG (Rust via wasm-bindgen) | PNG 解码 |
@jsquash/webp | libwebp | WebP 解码和编码 |
bmp-js | 纯 JavaScript | BMP 解码 |
每个图像经过三阶段管道:
读取文件 (I/O) --> 解码为 RGBA (WASM) --> 编码为 WebP (WASM) --> 写入文件 (I/O)
关键含义: WASM 编解码器在主线程中运行(不是本机 C)。这意味着:
- 解码 + 编码是 CPU 绑定的,运行速度大约是等效操作的本机
cwebp的 60-80%。 - WASM 初始化是一次性成本。 四个 WASM 模块(PNG 解码器、JPEG 解码器、WebP 解码器、WebP 编码器)在首次使用时从嵌入式二进制 blob 编译,然后为会话缓存。这增加了约 100-300ms 到首次调用。
- 每个图像的内存开销等于原始 RGBA 位图(宽度 x 高度 x 4 字节)加上编码的 WebP 缓冲区。一张 4000x3000 的照片需要约 48 MB 的临时内存。
并发模型#
GetWebP 使用使用 p-limit 的异步并发,而不是操作系统级线程或工作者线程。
+--> [解码 + 编码文件 1] --+
主线程 ------+--> [解码 + 编码文件 2] --+--> 结果
(事件循环) +--> [解码 + 编码文件 N] --+
- Pro 计划默认为
os.cpus().length - 1个并发任务(上限为 32)。可通过--concurrency配置。 - 免费计划强制串行(一次 1 个任务),文件之间有 3 秒延迟。
因为 WASM 执行在解码/编码期间阻止 JavaScript 事件循环,真正的并行性是有限的。然而,I/O 操作(文件读取和写入)与其他任务中的 CPU 工作重叠,产生可衡量的吞吐量增益,大约是 CPU 核心数。
注意: 代码库中存在
worker_threads实现但未激活。jSquash WASM 模块目前无法可靠地在 Node.js 工作者线程内初始化。如果这在上游解决,线程级并行性将解锁近线性扩展。
基准方法#
测试环境#
重要: 下面的所有数字都是模板。实际结果取决于您的硬件、操作系统、磁盘速度和图像内容。将
[benchmark pending]值替换为您自己的测量。
| 参数 | 值 |
|---|---|
| CPU | [benchmark pending] (例如 Apple M2 Pro,10 核) |
| RAM | [benchmark pending] (例如 16 GB) |
| 磁盘 | [benchmark pending] (例如 NVMe SSD) |
| 操作系统 | [benchmark pending] (例如 macOS 14.4) |
| 运行时 | Bun 编译的二进制 (v1.0.1) |
| 质量 | 默认 (-q 80),除非另有说明 |
测试数据集#
| 类别 | 数量 | 平均大小 | 分辨率范围 |
|---|---|---|---|
| 小 JPEG | 50 | ~200 KB | 800x600 -- 1200x900 |
| 大 JPEG | 50 | ~3 MB | 4000x3000 -- 6000x4000 |
| PNG (照片) | 50 | ~5 MB | 3000x2000 -- 4000x3000 |
| PNG (图形/截图) | 50 | ~800 KB | 1920x1080 |
| BMP | 20 | ~10 MB | 3000x2000 |
| WebP (重新编码) | 20 | ~400 KB | 2000x1500 |
测量方法#
# 单个文件
time getwebp convert photo.jpg -o /tmp/out
# 批量(墙钟时间)
time getwebp convert ./dataset -o /tmp/out --concurrency 4所有时间都是墙钟时间。每个测试运行 3 次;报告中位数。
单文件基准#
在默认质量 (-q 80) 时转换单个图像的时间。
JPEG#
| 输入大小 | 分辨率 | 时间 | 输出大小 | 节省 |
|---|---|---|---|---|
| 200 KB | 1200x900 | [benchmark pending] (~0.3s) | [pending] | [pending] (~25--40%) |
| 1 MB | 2400x1800 | [benchmark pending] (~0.6s) | [pending] | [pending] (~30--45%) |
| 3 MB | 4000x3000 | [benchmark pending] (~1.2s) | [pending] | [pending] (~30--50%) |
| 8 MB | 6000x4000 | [benchmark pending] (~2.5s) | [pending] | [pending] (~35--55%) |
PNG#
| 输入大小 | 分辨率 | 类型 | 时间 | 输出大小 | 节省 |
|---|---|---|---|---|---|
| 300 KB | 1920x1080 | 截图 | [benchmark pending] (~0.4s) | [pending] | [pending] (~70--85%) |
| 2 MB | 3000x2000 | 照片 | [benchmark pending] (~1.0s) | [pending] | [pending] (~60--75%) |
| 5 MB | 4000x3000 | 照片 | [benchmark pending] (~2.0s) | [pending] | [pending] (~65--80%) |
| 15 MB | 6000x4000 | 照片 (alpha) | [benchmark pending] (~4.0s) | [pending] | [pending] (~55--70%) |
PNG 到 WebP 的转换通常产生最高的节省,因为 PNG 是无损的,而质量为 80 的 WebP 应用有损压缩。
BMP#
| 输入大小 | 分辨率 | 时间 | 输出大小 | 节省 |
|---|---|---|---|---|
| 5 MB | 1920x1080 | [benchmark pending] (~0.5s) | [pending] | [pending] (~95%+) |
| 36 MB | 4000x3000 | [benchmark pending] (~2.5s) | [pending] | [pending] (~97%+) |
BMP 文件是未压缩的位图。转换为 WebP 产生戏剧性的文件大小缩减。请注意,BMP 解码使用纯 JavaScript 库 (bmp-js) 而不是 WASM,对于非常大的文件来说更慢但足以满足典型用途。
WebP (重新编码)#
| 输入大小 | 分辨率 | 时间 | 输出大小 | 节省 |
|---|---|---|---|---|
| 200 KB | 2000x1500 | [benchmark pending] (~0.4s) | [pending] | [pending] (varies) |
WebP 到 WebP 重新编码对于调整质量很有用。节省取决于输入和输出之间的质量差距。
批量吞吐量#
并发扩展 (Pro)#
50 个 JPEG 文件,平均 3 MB 每个,在 8 核机器上:
--concurrency | 墙钟时间 | 吞吐量 (文件/秒) | 相对串行的加速 |
|---|---|---|---|
| 1 | [benchmark pending] (~60s) | [pending] (~0.8) | 1.0x |
| 2 | [benchmark pending] (~35s) | [pending] (~1.4) | ~1.7x |
| 4 | [benchmark pending] (~20s) | [pending] (~2.5) | ~3.0x |
| 7 (8 核上的默认值) | [benchmark pending] (~14s) | [pending] (~3.6) | ~4.3x |
| 8 | [benchmark pending] (~13s) | [pending] (~3.8) | ~4.6x |
| 16 | [benchmark pending] (~12s) | [pending] (~4.0) | ~5.0x |
| 32 | [benchmark pending] (~12s) | [pending] (~4.0) | ~5.0x |
为什么扩展是次线性的: GetWebP 在单个进程内使用异步并发,而不是操作系统线程。WASM 编解码器执行在每次解码/编码调用期间阻止事件循环。并发收益来自 I/O(文件读取/写入)与其他任务中 CPU 工作的重叠。超过 CPU 核心数,额外的并发增加调度开销,不会改善吞吐量。
推荐设置: 保留为默认值(CPU 核心数 - 1)。将 --concurrency 设置高于您的核心数会带来收益递减。
大型批量 (1,000 文件)#
混合数据集:600 JPEG + 300 PNG + 100 BMP,各种大小,8 核机器上的默认并发:
| 指标 | 值 |
|---|---|
| 总文件数 | 1,000 |
| 墙钟时间 | [benchmark pending] (~4--6 min) |
| 每个文件的平均时间 | [benchmark pending] (~0.3s) |
| 总输入大小 | [benchmark pending] (~2 GB) |
| 总输出大小 | [benchmark pending] (~800 MB) |
| 整体节省 | [benchmark pending] (~60%) |
| 峰值内存 | [benchmark pending] (~500 MB) |
计划对比#
在 8 核机器上处理 50 个 JPEG 文件(平均 3 MB 每个):
| 指标 | 免费 | Pro |
|---|---|---|
| 文件限制 | 每次运行 20 个 | 无限制 |
| 处理模式 | 串行 + 3 秒延迟 | 并行 (7 个并发) |
| 10 个文件的时间 | [pending] (~42s) | [pending] (~3s) |
| 50 个文件的时间 | N/A (上限为 20) | [pending] (~14s) |
| 有效吞吐量 | ~0.24 文件/秒 | ~3.6 文件/秒 |
| 加速 | -- | ~15x |
免费计划时间分解 (10 文件)#
文件 1: ~1.2s (转换)
3.0s (强制延迟)
文件 2: ~1.2s
3.0s
...
文件 10: ~1.2s
─────────────────────────
总计: ~12s 转换 + ~27s 延迟 = ~39s
免费计划上的 3 秒文件间延迟是主要瓶颈,而不是转换本身。升级到 Pro 完全移除此延迟并启用并行处理。
质量 vs 文件大小 vs 速度#
所有测量都在单个 4000x3000 JPEG (~3 MB) 上:
质量 (-q) | 输出大小 | 节省 | 编码时间 | 视觉质量 |
|---|---|---|---|---|
| 50 | [pending] (~300 KB) | [pending] (~90%) | [pending] (更快) | 明显的伪影 |
| 60 | [pending] (~400 KB) | [pending] (~87%) | [pending] | 轻微伪影 |
| 75 | [pending] (~600 KB) | [pending] (~80%) | [pending] | 好 |
| 80 (默认) | [pending] (~700 KB) | [pending] (~77%) | [pending] | 非常好 |
| 90 | [pending] (~1.2 MB) | [pending] (~60%) | [pending] | 优秀 |
| 95 | [pending] (~1.8 MB) | [pending] (~40%) | [pending] | 近无损 |
| 100 | [pending] (~2.5 MB) | [pending] (~17%) | [pending] (更慢) | 无损 WebP |
建议: 质量 75-80 为网络传输提供最佳的文件大小和视觉保真度平衡。对于摄影作品集或打印质量资产使用 90+。
与其他工具的对比#
所有测试都在相同的 50 文件 JPEG 数据集上(平均 3 MB,4000x3000),为公平比较,单线程:
| 工具 | 类型 | 时间 (50 文件,串行) | 平均节省 | 注意 |
|---|---|---|---|---|
| GetWebP CLI | WASM (jSquash 的 libwebp) | [benchmark pending] (~60s) | [pending] (~35%) | 跨平台二进制,无依赖 |
| cwebp | 本机 C (libwebp) | [benchmark pending] (~40s) | [pending] (~35%) | Google 的参考编码器 |
| sharp | 本机 C (libvips + libwebp) | [benchmark pending] (~25s) | [pending] (~35%) | 需要 Node.js + 本机插件 |
| Squoosh CLI | WASM (相同编解码器) | [benchmark pending] (~65s) | [pending] (~35%) | 已弃用,类似 WASM 方法 |
| ImageMagick | 本机 C | [benchmark pending] (~80s) | [pending] (~30%) | 通用,不是 WebP 优化的 |
分析#
GetWebP vs cwebp: 两者都使用 libwebp 编码,因此在相同质量设置下的输出质量和文件大小几乎相同。性能差距来自 WASM 开销(比本机慢 1.3-1.6 倍)。GetWebP 的优势是零依赖的跨平台分发和具有并发的批处理。
GetWebP vs sharp: Sharp 直接链接到本机 libvips,使其成为最快的选项。但是,它需要 Node.js 和平台特定的本机二进制编译。GetWebP 作为自包含的 Bun 编译的二进制文件提供。
GetWebP vs Squoosh CLI: 两者都使用相同的 jSquash/Squoosh WASM 编解码器。性能相当。Squoosh CLI 已弃用;GetWebP 得到积极维护。
GetWebP vs ImageMagick: ImageMagick 的 WebP 编码器通常不如 libwebp 优化。GetWebP 在等效的视觉质量下产生更小的文件。
何时选择 GetWebP#
- CI/CD 管道: 单个二进制文件,无运行时依赖,JSON 输出,用于脚本的退出代码。
- 跨平台团队: macOS (ARM + Intel)、Linux 和 Windows 上相同的二进制文件。
- 批量作业: 内置并发、递归扫描、
--skip-existing用于增量构建。 - 无本机编译: 避免 sharp 需要的
node-gyp/ 平台特定插件问题。
并行吞吐量对比#
当考虑并发时,差距缩小。8 核机器上的 50 个 JPEG 文件:
| 工具 | 模式 | 墙钟时间 | 注意 |
|---|---|---|---|
| GetWebP CLI | --concurrency 7 | [benchmark pending] (~14s) | 内置批处理 |
| cwebp + xargs | xargs -P 7 | [benchmark pending] (~10s) | 需要 shell 脚本 |
| sharp (自定义脚本) | 工作者池 | [benchmark pending] (~7s) | 需要 Node.js + 自定义代码 |
GetWebP 的内置并发使其与需要手动并行化包装器的本机工具竞争。
何时本机工具可能更好#
- 最大吞吐量: 如果处理数百万图像,每毫秒都很重要,本机
cwebp或 sharp 将每个图像更快。 - 高级转换: 如果您还需要调整大小、裁剪或超越 WebP 的格式转换,sharp 或 ImageMagick 提供更广泛的功能集。
内存使用#
GetWebP 的内存消耗由解码/编码期间的原始像素缓冲区主导:
| 因素 | 内存影响 |
|---|---|
| WASM 模块初始化 | ~20--40 MB(一次性,4 个编解码器) |
| 每个图像解码缓冲区 | width * height * 4 字节 (RGBA) |
| 每个图像编码缓冲区 | 输出 WebP 大小(更小得多) |
| 并发图像 | concurrency * per-image memory |
示例:8 核机器,默认并发 (7)#
处理 4000x3000 图像:
WASM init: ~30 MB
7 个并发图像: 7 * 48 MB = ~336 MB
开销 (缓冲区、GC): ~50 MB
────────────────────────────
峰值估计: ~416 MB
减少内存使用#
- 较低的并发:
--concurrency 2将相同图像的峰值内存减少到 ~130 MB。 - 较小的图像: Web 分辨率图像 (1920x1080) 每个解码缓冲区使用 ~8 MB,并发 7 时总共 ~90 MB。
- BMP 警告: 大 BMP 文件需要额外的全帧缓冲区用于 BGR 到 RGB 通道交换。一个 36 MB 的 BMP (4000x3000) 在解码期间分配 ~96 MB。
启动时间#
| 阶段 | 持续时间 | 注意 |
|---|---|---|
| 二进制加载 | [benchmark pending] (~50ms) | Bun 编译的单个二进制文件 |
| WASM 初始化 | [benchmark pending] (~150ms) | 从嵌入式 blob 编译 4 个 WASM 模块 |
| 许可证检查 | [benchmark pending] (~5ms 本地,~500ms 网络) | JWT 在本地验证;仅在首次认证或过期时进行网络调用 |
| 文件扫描 | [benchmark pending] (~10ms 每 1,000 文件) | fs.readdir 带排序 |
| 总冷启动 | ~200--300ms | 后续运行重用操作系统文件缓存 |
对于单文件转换,启动开销是总时间的重要部分。对于批量作业 (100+ 文件),它可以忽略不计。
优化技巧#
为了最大吞吐量#
# 在专用构建机器上使用所有核心
getwebp convert ./images -r --concurrency 8
# 在增量构建中跳过已转换的文件
getwebp convert ./images -r -o ./dist --skip-existing
# 降低质量以加快编码(质量 < 80 稍微更快)
getwebp convert ./images -q 70为了最小内存#
# 限制并发以减少峰值内存
getwebp convert ./images --concurrency 2
# 一次处理一个目录而不是递归
getwebp convert ./images/thumbs -o ./out/thumbs
getwebp convert ./images/photos -o ./out/photos为了 CI/CD 管道#
# JSON 输出 + 退出代码用于脚本化的错误处理
getwebp convert ./assets -r -o ./dist --skip-existing --json
echo "Exit code: $?"
# 干运行以在转换前验证
getwebp convert ./assets -r --dry-run --json运行您自己的基准#
为您的特定硬件生成数字:
# 准备测试数据集
mkdir -p /tmp/bench-input /tmp/bench-output
# 复制或生成测试图像到 /tmp/bench-input
# 单文件时间
time getwebp convert /tmp/bench-input/sample.jpg -o /tmp/bench-output
# 使用默认并发的批量
time getwebp convert /tmp/bench-input -o /tmp/bench-output
# 具有 JSON 输出以获得机器可解析的结果的批量
getwebp convert /tmp/bench-input -o /tmp/bench-output --json > results.json
# 并发扫描
for c in 1 2 4 8 16; do
rm -rf /tmp/bench-output/*
echo "concurrency=$c"
time getwebp convert /tmp/bench-input -o /tmp/bench-output --concurrency $c
done
# 内存监控 (macOS)
/usr/bin/time -l getwebp convert /tmp/bench-input -o /tmp/bench-output
# 内存监控 (Linux)
/usr/bin/time -v getwebp convert /tmp/bench-input -o /tmp/bench-output写入安全#
GetWebP 使用原子写入来防止损坏的输出文件:
- 编码输出写入到临时文件 (
<name>.webp.tmp)。 - 临时文件通过
fs.rename重命名为最终路径 (<name>.webp)。 - 在 SIGINT (Ctrl+C) 上,任何正在进行中的
.tmp文件都会自动清理。
这意味着崩溃或中断永远不会在输出目录中留下半写入的 .webp 文件。已完成的文件是安全的。在下次运行时使用 --skip-existing 从您离开的地方恢复。
已知限制#
| 限制 | 影响 | 解决方案 |
|---|---|---|
| 单线程 WASM | 编码/解码阻止事件循环;扩展围绕核心数达到平台期 | 对于极端吞吐量需求,使用本机 cwebp |
| 无 GPU 加速 | WASM 编解码器仅为 CPU | N/A (libwebp 按设计仅为 CPU) |
| BMP 解码为纯 JS | 对于大 BMP 比 WASM 更慢 | 如果处理许多大 BMP,首先将 BMP 转换为 PNG |
| 免费计划延迟 | 每个文件 3 秒,20 文件上限 | 升级到 Pro |
| 内存随并发扩展 | 大图像上的高并发可能会耗尽 RAM | 对于大分辨率批量减少 --concurrency |