Skip to content

Performance Baseline

51hhh edited this page Sep 16, 2026 · 1 revision

性能基线

criterion 基准

基准在 src-tauri/benches/,通过 src-tauri/src/bench_support.rs 调生产代码——基准不复制实现,否则量到的是基准文件里那份副本。

cd src-tauri && cargo bench

门禁只编译不运行(--all-targets 会编译它们)。完整数字与已知坑见仓库内 docs/bench-baseline.md。

首次基线(中位数)

基准 中位数 说明
encode_png_1080p 77.1 ms 1080p 渐变帧,CompressionType::Fast
png_dimensions_1080p 505 ns 只解 PNG 头
validate_png_1080p 20.0 ms 完整解码,只在信任边界上用
validate_png_1440p 36.3 ms 2560×1440,截图提交的常见最坏尺寸
decode_png_base64_1080p 1.27 ms 前端回传的 data URL 解码
compute_hash_1mib 1.56 ms 1 MiB 文本的 SHA-256 去重哈希
rgba_fingerprint_1080p 1.47 ms 轮询之间「还是上一张图吗」
strip_html_tags_1mib 1.41 ms 逐字符扫描 1 MiB HTML
is_sensitive_text_prefix_hit 4.2 ns 命中前缀提前返回
is_sensitive_text_worst_case_100kib 31.2 µs 不命中,走整段转小写
get_clips_first_page 23.2 µs 2000 行内存库,无查询,limit 50
get_clips_fts_prefix 298 µs ≥3 字符走 FTS5 前缀查询
get_clips_like_fallback_short_query 38.1 µs <3 字符走 LIKE 回退
get_clips_no_match 259 µs 空结果

两个来自基准的实际优化

只解 PNG 头:png_dimensions 从完整解码的 19.6 ms 降到 505 ns(约 39000 倍)。凡是只需要宽高的地方(缩略图缓存判断、尺寸校验、贴图 origin 查找)都不该整张解码。

指纹替代重编码:轮询之间判断图片是否变化,用 rgba_fingerprint(1.47 ms)替掉一次完整 PNG 编码(77 ms)。

不做的基准

裁剪基准故意不做:裁剪是 memcpy,被同路径的 encode_png 掩盖两个数量级。给它单独做基准量到的是噪声。

内存优化

已落地的措施:

措施 说明
glibc malloc 调优 MALLOC_ARENA_MAX / MMAP_THRESHOLD / TRIM_THRESHOLD
WebKit 精简 禁用 GPU 加速与不需要的功能
缓存模型 CacheModel / clear_cache
延迟导入 大库按需加载
缩略图 FIFO 有界缓存
模糊释放 用完即释放
Cargo release profile strip / lto = "fat" / codegen-units = 1 / opt-level = "s" / panic = "abort"

关键路径的归因结论

这些是量出来的,不是猜的。记录归因比记录数字更重要——数字会随硬件变,归因不会。

截图慢在 PNG 编码,不在绘制

4K 屏一次取帧 1704 ms 中,1607 ms(94%)是合成器 deflate PNG,绘制加读回只有约 100 ms。

因此「少拍像素」无效(逐屏两次 1830 ms vs 整屏一次 1900 ms)。真正的修法是换数据源——改从视频帧接口取,实测 1900 ms → 280 ms,同时还拿到原生分辨率。详见截图与冻结帧。

贴图糊在缩放链路,不在解码

分数缩放桌面上,图片先被平滑放大再被合成器缩回,第一趟就丢了细节。反投影预补偿把屏上 PSNR 从 30.28 dB 提到 43.02 dB。详见显示缩放。

列表卡在全尺寸解码,不在 DOM

列表行取原图会让每次开面板都把十几 MB 送进 webview 并做十几次全尺寸 PNG 解码,全落在 webview 那一个线程上。改走后端缩略图(最长边 128 px,按 id 缓存)后消失。

图片历史的解码搬离 async worker

原图读取、base64 编码与 128 px 缩略图解码/缩放都放到 blocking pool。冷缓存同时出现多张截图时不再占满 Tauri async worker,并以两路并发限制约束全尺寸解码的 CPU / RSS 峰值。缓存命中路径保持直接返回。

贴图协议绕开三层转换

贴图显示图走按窗口 label / revision 隔离的原生协议,响应直接接管 PNG 字节,不再经过 Rust base64 → JSON → JS 解码 → Blob URL。复制、保存和画布仍从后端权威原图读取。

拖动窗口只保留一个 debounce

原实现为每个 Moved 事件创建睡眠线程,持续拖动时线程与栈内存持续增长。改为全局单个位置保存 debounce worker,仍在停止移动 300 ms 后保存最终坐标。

工程 PNG 不常驻重复字符串

打开可编辑 PNG 后,运行时只保留唯一一份原图字节和不含 base64 的轻量工程元数据。每张工程不再常驻约为原图 PNG 1.33 倍的重复字符串;再次保存时才临时重建完整自包含容器。

链接预览缓存有界

清理 7 天前的过期记录,并只保留最近 512 条,避免数据库随历史链接无限增长。

长历史实测

GNOME Wayland / WebKitGTK 隔离环境,1 万条混合历史(含 2000 张图片):

  • 全过程只挂载 10–15 行
  • 完整向下、向上、再次向下遍历均无滚动失败、崩溃或 OOM
  • 第二次遍历后 PSS 相对首次遍历末端只增加约 9 MiB
  • 显式隐藏后列表对象从 1 万条归零,15 秒后 cgroup PSS 回落约 8 MiB

另有连续五次贴图关闭的观察:service cgroup PSS 在首次建立 WebKit / 图像分配高水位后稳定,没有随关闭次数线性增长。

报告性能数字的纪律

  • 带调试器、带大量诊断输出或带断点的运行不是性能基准
  • 单次测量不构成基准,criterion 的中位数才是
  • 一张合成图的识别耗时不能外推到所有真实图片
  • 「进程还活着」「函数返回成功」都不能替代实际耗时与内存的测量

Clone this wiki locally