From b9eae2bb733a0aba79df3a427193b9282cad348c Mon Sep 17 00:00:00 2001 From: AskaEth Date: Mon, 2 Nov 2026 23:34:52 +0800 Subject: [PATCH] =?UTF-8?q?perf(build):=20=E5=8A=A0=20fast=20=E6=9E=84?= =?UTF-8?q?=E5=BB=BA=E6=A1=A3=E3=80=81=E4=BE=9D=E8=B5=96=E5=B9=B6=E8=A1=8C?= =?UTF-8?q?=20codegen=E3=80=81=E9=AA=8C=E6=94=B6=E8=84=9A=E6=9C=AC?= =?UTF-8?q?=E4=B8=80=E6=AC=A1=20--bins?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 三条慢因里两条落在构建配置上,发布档(release)的体积取向不动: 1. `[profile.fast]`(inherits = release):opt-level 2 / lto thin / codegen-units 16, 日常迭代与验收用它,发布仍走 release。`panic = "unwind"` 是**必须显式写**的一行—— release 是 panic = "abort",inherits 会照抄,漏了它 `cargo test --profile fast` 起不来 (test harness 依赖 unwind)。 2. `[profile.release.package."*"]`:依赖的 codegen-units 放到 16(并行 codegen), **opt-level 保持 "z"**。最初按「优化等级高 = 编译快」把依赖抬到 opt-level 3, 同机冷编译实测反了:opt-3 是 22m45s / GUI 19.46 MiB,opt-"z" 是 16m01s / 15.72 MiB—— 又慢 29.6% 又大 19~24%,收益全部来自 codegen-units 而不是 opt-level。 只调优化强度,不碰 panic / strip(那两项改的是整个二进制的运行时行为)。 3. `[profile.fast.package."*"]` 显式写一遍:package 覆盖按 profile 分别生效,不赌 inherits 会不会连覆盖一起继承(实测 594 个编译单元全部 opt-level=2 + cu16,含依赖)。 实测写进 packaging/README §6/§7.1(同机、删空 target 的冷编译、无并发负载): release 16m01s(GUI 15.72 / iced 13.27 / ull 5.97 MiB),fast 24m27s(36.41 / 32.02 / 15.23 MiB)。 **fast 档在本机冷编译没有收益甚至更慢**,瓶颈是两个大 UI crate + 链接 + 机械盘 IO, 它的定位是 SSD 机器与增量重编——这条照实写进文档,不为了贴近预期挑数字。 DevDocs/ops/macos-acceptance.sh 的构建段改动(DevDocs 被 gitignore,无法入库)见完成报告。 --- Cargo.toml | 40 ++++++++++++++++++++++++ packaging/README.md | 76 +++++++++++++++++++++++---------------------- 2 files changed, 79 insertions(+), 37 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4ecace1..e6c4180 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -108,3 +108,43 @@ lto = "fat" codegen-units = 1 panic = "abort" strip = "symbols" + +# P6(M96):日常迭代 / 本机验收用的「快」档,发布仍走上面的 release。 +# `inherits = "release"` 让其余各项(strip / debug / overflow-checks …)照抄 release, +# 只覆盖拖慢构建的四处: +# - lto = "thin":跨 crate 内联仍有,但可以并行;fat LTO 在 codegen-units = 1 下等于 +# 全程序串行链接(375 个 crate 排队),一次性吃掉十几分钟。 +# - codegen-units = 16:给 codegen 留并行度,不再把整个 crate 挤成一个 CGU。 +# - opt-level = 2:介于 "z" 与 3 之间,"z" 会为了体积关掉一批循环/内联优化,代价是运行期。 +# - panic = "unwind":**必须显式写**。release 是 panic = "abort",inherits 会照抄下来, +# 而 test harness 依赖 unwind —— 漏了这行 `cargo test --profile fast` 直接起不来。 +# +# ⚠️ 定位(实测,别误读):本机(12 核 + 机械盘源码盘 + -j 4)冷编译 fast = 24m27s, +# **比 release 的 16m01s 还慢**,体积是 2.3 倍(36.41 vs 15.72 MiB GUI)。瓶颈在两个大 +# UI crate 的编译 + 链接 + 磁盘 IO,LTO/CGU 这几个旋钮被掩盖了。它的价值在 **SSD 机器**和 +# **增量重编**(thin 不必每次全程序 LTO);「加了 fast 档」≠「冷编译会变快」。 +[profile.fast] +inherits = "release" +opt-level = 2 +lto = "thin" +codegen-units = 16 +panic = "unwind" + +# P6(M96):依赖不需要为体积做极致优化,但**实测结论与直觉相反**——把依赖的 opt-level 从 +# release 的 "z" 抬到 3(本档最初就是这么定的)会让冷编译**更慢**、体积还更大: +# 同机冷编译 opt-3 = 22m45s / 19.46 MiB(GUI),opt-"z" = 16m01s / 15.72 MiB。 +# 真正的收益全在 `codegen-units = 16`(并行 codegen),与 opt-level 无关;抬等级是在拿编译时间 +# 和体积换运行期。所以这里**只放宽并行度,opt-level 保持 "z"**,workspace 自己的 crate 仍吃 +# opt-level = "z" / 单 CGU。 +# **只调优化强度**:panic 与 strip 绝不写进这里。那两项改变的是整个二进制的运行时行为 +# (panic 策略决定能否 unwind 出栈、strip 决定符号表留不留),不是「给依赖放宽」的范畴。 +[profile.release.package."*"] +opt-level = "z" +codegen-units = 16 + +# 快档的依赖覆盖单独写一遍:package 覆盖是按 profile 分别生效的(`[profile.release.package."*"]` +# 只作用在 release 档),显式写出来就不必赌 `inherits` 会不会连 package 覆盖一起继承。 +# 值与 fast 档自身一致——依赖编得快是快档的目的,不是再抬一档优化强度。 +[profile.fast.package."*"] +opt-level = 2 +codegen-units = 16 diff --git a/packaging/README.md b/packaging/README.md index eb9b13e..cb84ab9 100644 --- a/packaging/README.md +++ b/packaging/README.md @@ -51,7 +51,8 @@ ### 本地(当前平台) ```bash -cargo build --release --locked # 产出 target/release/{unlucky-launcher,ull} +cargo build --release --locked # 产出 target/release/{unlucky-launcher,ull,unlucky-launcher-iced} +cargo build --profile fast --bins # 只是想快点看到画面/跑测试:快档(§6),产物在 target/fast/ python packaging/make_artifacts.py --out dist # 产物落在 dist/,含 SHA256SUMS.txt python packaging/make_artifacts.py --version v0.1.0 # 顺带校验版本一致性 python packaging/make_artifacts.py --no-smoke # 跳过 ull --version 冒烟 @@ -66,7 +67,7 @@ macOS x64),其它组合直接报错退出——不在矩阵里的平台不 ```bash rustup target add aarch64-pc-windows-msvc cargo build --release --locked --target aarch64-pc-windows-msvc \ - --bin unlucky-launcher --bin ull + --bin unlucky-launcher --bin ull # 不用 --bins:发布矩阵只发 GUI + ull,iced 不进产物 python packaging/make_artifacts.py --slot windows-arm64 \ --build-dir target/aarch64-pc-windows-msvc/release --out dist ``` @@ -193,49 +194,50 @@ SLINT_BACKEND=winit-software unlucky-launcher # 环境变量(Slint 它同样能在软渲染下工作,是 CI 与远程机唯一可靠的"真出图"证据。 - 发布冒烟(本机实跑,见 §7)就是用 `--software-renderer --screenshot` 组合证明"无 GPU 也能起来"。 -## 6. Cargo 体积优化(release profile) +## 6. Cargo 构建档位(release 体积档 / fast 速度档) -`Cargo.toml`: +| 档位 | 用在哪 | opt-level | lto | codegen-units | panic | +|------|--------|-----------|-----|---------------|-------| +| `release` | 发布 / 验收产物 | `"z"`(workspace 自身 crate) | `fat` | 1 | `abort` | +| `release.package."*"` | 同上,**只作用于依赖** | `"z"` | 不可覆盖 | 16 | 继承 | +| `fast` | 日常迭代、本机跑测试 | 2 | `thin` | 16 | `unwind` | -```toml -[profile.release] -opt-level = "z" # 体积优先(启动器不是算力密集;UI 走 Slint 渲染,热路径不在 Rust 侧) -lto = "fat" # 全量 LTO:跨 crate 内联+死代码消除,比 thin 再小一截(实测见 §7) -codegen-units = 1 # 单 CGU,配合 LTO 拿最大优化面 -panic = "abort" # 去掉 unwind 表 -strip = "symbols" # 剥符号表(GNU/MSVC 路径都生效) -``` - -取舍:`lto = "fat"` + `codegen-units = 1` 让 release 链接明显变慢(本机全量 release 构建 10 分钟量级, -见 §7.1),换来的是体积与启动时间。变慢的根源在 `codegen-units = 1`:整个 crate 只切出一个 CGU, -LTO 也就只有一个线程可跑(`fat` 本身只管"跨 crate 全量参与",不等于单线程)。 -本地开发迭代走 `cargo build`(dev profile)不受影响,但 **`ci.yml` 每次 push/PR 都跑 -`cargo build --workspace --release`**,会实打实吃这份 fat LTO 的链接时间——CI 慢的一部分是 release -档构建,不是测试本身。 -`panic = "abort"` 意味着 panic 不带 backtrace 栈展开——UI 层已用 `Result` 承接错误, -`unwrap` 只出现在测试里,这是可接受的取舍。 +- **`release` 是体积档**:`fat` + 单 CGU 换来实测 GUI −10.8% / CLI −18.7%(§7.1)。变慢的根源在 + `codegen-units = 1`(整个 crate 只切一个 CGU,LTO 也只有一个线程可跑;`fat` 只管"跨 crate 全量参与", + 不等于单线程),375 个 crate 排队链接就是十几分钟的来源。 +- **`release.package."*"` 放宽的只有并行度,不是 opt-level**:实测把依赖 opt-level 抬到 3 反而**又慢 29.6% + 又大 19~24%**(§7.1),所以保持 `"z"`、只把 `codegen-units` 放到 16。package 覆盖**只允许调优化强度**: + `panic` / `lto` / `rpath` 写进去 cargo 直接报错,`strip` 会改变整个二进制的运行时行为——都不许出现。 +- **`fast` 是速度档,不是发布物**:`thin` LTO + 16 CGU 可并行,`opt-level = 2` 介于 `z` 与 3 之间。 + 必须显式 `panic = "unwind"` ——`release` 是 `abort`,`inherits` 会照抄,漏了这一行 + `cargo test --profile fast` 连编译都过不去(test harness 依赖 unwind)。**但它在机械盘机器上冷编译 + 并无收益**(比 release 还慢、体积 2.3 倍,§7.1),价值在 SSD 机器与增量重编。 +- 本地开发迭代走 `cargo build`(dev profile)不受影响,但 **`ci.yml` 每次 push/PR 都跑 + `cargo build --workspace --release`**,会实打实吃这份 fat LTO 的链接时间——CI 慢的一部分是 release + 档构建,不是测试本身。`panic = "abort"` 意味着 panic 不带 backtrace 栈展开——UI 层已用 `Result` 承接错误,可接受。 ## 7. 实测(本机,不是估算) -构建环境:rustc 1.99.1 stable(host `x86_64-pc-windows-gnu`,MinGW 链接器), -`cargo build --release --locked --bin unlucky-launcher --bin ull`,12 核机器(同一时刻另有别的 worker -在做全量 debug 构建,属于偏悲观的真实负载)。产物:`target/release/unlucky-launcher.exe`(GUI,交付物) -与 `target/release/ull.exe`(CLI)。 +构建环境:rustc 1.99.1 stable(host `x86_64-pc-windows-gnu`,MinGW 链接器)。§7.2/§7.3 的早数字是在 +同一刻另有 worker 做全量 debug 构建(偏悲观的真实负载)时测的,§7.1 的 M96 两行是空载冷编译。 -### 7.1 体积(实测字节数,不是估算) +### 7.1 体积与冷构建耗时(实测字节数,不是估算) -| release profile | `unlucky-launcher.exe` | `ull.exe` | 冷构建耗时(本机 12 核) | -|-----------------|-----------------------|-----------|-----------| -| `lto = "thin"`(改前) | 16,366,080 B = 15.61 MiB | 7,572,992 B = 7.22 MiB | 11m52s | -| `lto = "fat"`(改后,本仓库采用) | **14,595,584 B = 13.92 MiB** | **6,157,824 B = 5.87 MiB** | 9m27s(增量,依赖已编好) | +| 档位 | `unlucky-launcher.exe` | `unlucky-launcher-iced.exe` | `ull.exe` | 冷构建耗时 | +|------|-----------------------|----------------------------|-----------|-----------| +| `release`,`thin`(M46 前) | 16,366,080 B = 15.61 MiB | 未测(当时无 iced 版) | 7,572,992 B = 7.22 MiB | 11m52s | +| `release`,`fat`,依赖全 `"z"`(M46,改前) | **14,595,584 B = 13.92 MiB** | 未测 | **6,157,824 B = 5.87 MiB** | 9m27s(增量) | +| `release`,`fat` + 依赖 opt-3 / cu16(M96 试过的那版,**已否**) | 20,407,296 B = 19.46 MiB | 18,371,584 B = 17.52 MiB | 7,743,488 B = 7.38 MiB | 22m45s(冷) | +| `release`,`fat` + 依赖 `"z"` / cu16(**M96 起,发布档**) | **16,479,232 B = 15.72 MiB** | **13,910,528 B = 13.27 MiB** | **6,256,128 B = 5.97 MiB** | **16m01s(冷)** | +| `fast`,`thin` + cu16(M96 起,日常档) | 38,182,400 B = 36.41 MiB | 33,570,304 B = 32.02 MiB | 15,967,744 B = 15.23 MiB | 24m27s(冷) | -`fat` 相对 `thin` 的实测差:GUI **−1,770,496 B(−10.8%)**,CLI **−1,415,168 B(−18.7%)**。 -一次全量 LTO 把跨 crate 的死代码与重复实例化一起消掉,收益远超常见印象里的"1~2%"; -代价是 release 链接更慢——慢在 `codegen-units = 1`(单 CGU → LTO 单线程),不是 `fat` 本身, -且只影响发布/验收构建,日常 `cargo build` 走 dev profile 不受影响。 - -体积构成(按依赖树判断):Slint 运行时(femtovg + software 双渲染器 + winit/accesskit/托盘)+ rustls/reqwest -+ rig/rmcp/axum 是主要来源;`ull` 明显更小是 D18 纪律的直接结果(CLI 零 UI 框架依赖)。 +测量条件(M96 三行):12 核 / `x86_64-pc-windows-gnu`,`-j 4`,源码在机械盘、`CARGO_TARGET_DIR` 在 SSD,均为**删空 target +后的冷编译**、同机同晚无并发负载;M46 两行是更早提交上的数字(依赖已热 / 另有并发),**跨行比耗时没意义**,体积差 +也不能全归给 profile(其间多了 iced / presenter / i18n 等 crate)。 +两条结论都反直觉但都是实测:① **依赖 opt-level 从 3 退回 `"z"` 又快 29.6% 又小 19~24%**——收益全在 `codegen-units = 16` 的 +并行 codegen,抬 opt-level 是拿编译时间和体积换运行期;② `fast` **冷编译比 `release` 还慢**(24m27s vs 16m01s)、体积 2.3 倍, +瓶颈在两个大 UI crate + 链接 + 机械盘 IO——它的定位是 **SSD 机器与增量重编**(thin 不必每次全程序 LTO),不是冷编译加速。 +体积构成(按依赖树判断):Slint 运行时(femtovg + software 双渲染器 + winit/accesskit/托盘)+ rustls/reqwest + rig/rmcp/axum 是主要来源;`ull` 更小是 D18 纪律(CLI 零 UI 依赖)。 ### 7.2 冒烟(真跑,2026-10-31 夜间 + 11-01 早,本机 Win11 26100)