From b9eae2bb733a0aba79df3a427193b9282cad348c Mon Sep 17 00:00:00 2001 From: AskaEth Date: Mon, 2 Nov 2026 23:34:52 +0800 Subject: [PATCH 1/2] =?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) From f6e55c5c7e7ce1e1a2956a4f6a55683f90ac2926 Mon Sep 17 00:00:00 2001 From: AskaEth Date: Tue, 3 Nov 2026 00:19:40 +0800 Subject: [PATCH 2/2] =?UTF-8?q?docs(build):=20=E4=BF=AE=E6=AD=A3=E4=B8=89?= =?UTF-8?q?=E5=A4=84=E3=80=8C=E6=96=87=E6=A1=A3=E8=AF=B4=E6=B3=95=E4=B8=8E?= =?UTF-8?q?=E5=AE=9E=E6=B5=8B=E4=B8=8D=E7=AC=A6=E3=80=8D=E7=9A=84=E6=B3=A8?= =?UTF-8?q?=E9=87=8A=EF=BC=88M96=20=E5=A4=8D=E5=AE=A1=20P2-1/2/3=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 只改措辞与注释,**profile 数值一个没动**(release.package 仍是 opt-level="z" + cu16, fast 仍是 opt-2/thin/cu16/unwind)。 P2-1 `panic = "unwind"` 的理由原本写错了:原注释说「漏了这一行 `cargo test --profile fast` 起不来(test harness 依赖 unwind)」。实测三连否掉:`cargo build --profile release -v` 的 rustc 参数里**有** `panic=abort`、`--profile fast` 里没有(这行确实生效,可作证据);但 `cargo test --profile release -p unlucky-i18n --no-run`(abort 档)照样 Finished 并产出测试 二进制,`-v` 里 test 单元**没有** `-Cpanic=`,真跑 23 个测试全过 —— cargo 对 test 目标自己强制 unwind。所以理由改成「让 fast 档编出来的**二进制**走 unwind」,并把「能跑 cargo test」明确标为 **没有区分度**的证据。顺带记下 fast 继承 `strip="symbols"` ⇒ 能 unwind 但没有符号回溯。 P2-2 `[profile.fast.package."*"]` 的注释把机制说反了:原注释说「package 覆盖按 profile 分别 生效,release 的不影响 fast」。实测删掉这块后,fast 档的**外部依赖**直接变成 `opt-level="z"` (`slotmap`:1 那次 3 个单元 opt-2 → 删除后 3 个单元 opt-z),说明 `inherits` 会把 release 的 package 覆盖一起带过来。所以这块是**承重墙**,删了依赖会静默退回 z。注释按实测重写。 P2-3 「收益全在 `codegen-units = 16`」删掉:三组数据的唯一自变量是 opt-level(两臂都是 cu16), cu1→cu16 的收益本轮**没有同条件对照**,已明确标成未测、留待后续归因。同时补一句:GUI 相对旧 基线 +12.9% 里「代码增长」与「依赖 cu16」未分离(ull 只 +1.7%,在 +1~3% 口径内)。 注:P2-1 那条原始错误表述来自 tower 的 mission 任务书,不是 worker 的臆造。 验证:`cargo build --release --bins` rc=0(16m21s,产物字节与 §7.1 记录完全一致: 6,256,128 / 13,910,528 / 16,479,232),`cargo metadata` 通过,README 仍为 400 行(未破门禁)。 --- Cargo.toml | 23 ++++++++++++++--------- packaging/README.md | 34 +++++++++++++++++----------------- 2 files changed, 31 insertions(+), 26 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e6c4180..faf86d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -116,8 +116,12 @@ strip = "symbols" # 全程序串行链接(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` 直接起不来。 +# - panic = "unwind":release 是 panic = "abort",inherits 会照抄。留着它是为了让 fast 档编出来的 +# **二进制**走 unwind(panic 会栈展开,不是直接 abort)。 +# ⚠️ 理由不是「不写就跑不了测试」:`cargo test` **不**依赖这一行 —— cargo 对 test 目标自己强制 +# unwind。实测 `cargo test --profile release -p unlucky-i18n`(release = abort 档)照跑,23 个 +# 测试全过,且 test 单元的 rustc 参数里**没有** `-Cpanic=`。别拿「cargo test 能跑」当这行生效的证据; +# 要看就用 `cargo build --profile {fast,release} -v` 比 rustc 参数:release 有 `panic=abort`,fast 没有。 # # ⚠️ 定位(实测,别误读):本机(12 核 + 机械盘源码盘 + -j 4)冷编译 fast = 24m27s, # **比 release 的 16m01s 还慢**,体积是 2.3 倍(36.41 vs 15.72 MiB GUI)。瓶颈在两个大 @@ -132,19 +136,20 @@ 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。 +# 同机冷编译 opt-3 = 22m45s / 19.46 MiB(GUI),opt-"z" = 16m01s / 15.72 MiB(两臂同为 cu16)。 +# 所以这里**opt-level 保持 "z",只把并行度放到 16**;workspace 自己的 crate 仍吃 "z" / 单 CGU。 +# ⚠️ **未测**:cu1 → cu16 的收益本轮没有同条件对照(两臂都是 cu16,唯一自变量是 opt-level), +# 所以「cu16 才是收益来源」不是实测结论,要归因得单独跑一轮 cu1 对照。另:GUI 体积相对旧基线 +# 涨了 12.9%(13.92 → 15.72 MiB),其中「代码增长」与「依赖 cu16」也没分离。 # **只调优化强度**:panic 与 strip 绝不写进这里。那两项改变的是整个二进制的运行时行为 # (panic 策略决定能否 unwind 出栈、strip 决定符号表留不留),不是「给依赖放宽」的范畴。 [profile.release.package."*"] opt-level = "z" codegen-units = 16 -# 快档的依赖覆盖单独写一遍:package 覆盖是按 profile 分别生效的(`[profile.release.package."*"]` -# 只作用在 release 档),显式写出来就不必赌 `inherits` 会不会连 package 覆盖一起继承。 -# 值与 fast 档自身一致——依赖编得快是快档的目的,不是再抬一档优化强度。 +# `inherits = "release"` 会把 `[profile.release.package."*"]` **一起继承过来**(实测:删掉下面这块, +# fast 档的外部依赖就从 opt-level 2 退回 `"z"`;workspace 成员不受 `*` 覆盖、仍是 fast 自己的 2)。 +# 所以这块**必须存在**,它是承重墙不是冗余兜底 —— 删掉会让 fast 档的依赖静默退回 `"z"`。 [profile.fast.package."*"] opt-level = 2 codegen-units = 16 diff --git a/packaging/README.md b/packaging/README.md index cb84ab9..2469991 100644 --- a/packaging/README.md +++ b/packaging/README.md @@ -202,24 +202,25 @@ SLINT_BACKEND=winit-software unlucky-launcher # 环境变量(Slint | `release.package."*"` | 同上,**只作用于依赖** | `"z"` | 不可覆盖 | 16 | 继承 | | `fast` | 日常迭代、本机跑测试 | 2 | `thin` | 16 | `unwind` | -- **`release` 是体积档**:`fat` + 单 CGU 换来实测 GUI −10.8% / CLI −18.7%(§7.1)。变慢的根源在 - `codegen-units = 1`(整个 crate 只切一个 CGU,LTO 也只有一个线程可跑;`fat` 只管"跨 crate 全量参与", - 不等于单线程),375 个 crate 排队链接就是十几分钟的来源。 +- **`release` 是体积档**:`fat` + 单 CGU 换来实测 GUI −10.8% / CLI −18.7%(§7.1)。变慢的根源在 `codegen-units = 1` + (单 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 机器与增量重编。 + `panic = "unwind"` 是给**编出来的二进制**留的(`release` 是 `abort`,`inherits` 会照抄);`cargo test` + **不**依赖它——cargo 对 test 目标自己强制 unwind(实测 release/abort 档下 `cargo test --profile release` + 照跑、23 个测试全过,test 单元的 rustc 参数里没有 `-Cpanic=`)。要看这行是否生效,比 `cargo build -v` + 的 rustc 参数:release 有 `panic=abort`、fast 没有。它继承 `strip="symbols"`,所以能 unwind 但**没有符号 + 回溯**——要栈回溯就给 fast 档加 `strip = false`,代价是体积再涨。 +- **`fast` 在机械盘机器上冷编译并无收益**(比 release 还慢、体积 2.3 倍,§7.1),价值在 SSD 机器与增量重编。 +- **`[profile.fast.package."*"]` 是承重墙**:`inherits` 会把 release 的 package 覆盖一起带过来(实测:删掉它,fast 档外部依赖静默退回 `"z"`),必须存在。 - 本地开发迭代走 `cargo build`(dev profile)不受影响,但 **`ci.yml` 每次 push/PR 都跑 - `cargo build --workspace --release`**,会实打实吃这份 fat LTO 的链接时间——CI 慢的一部分是 release - 档构建,不是测试本身。`panic = "abort"` 意味着 panic 不带 backtrace 栈展开——UI 层已用 `Result` 承接错误,可接受。 + `cargo build --workspace --release`**,会实打实吃这份 fat LTO 的链接时间——CI 慢的一部分是 release 档构建。 ## 7. 实测(本机,不是估算) -构建环境:rustc 1.99.1 stable(host `x86_64-pc-windows-gnu`,MinGW 链接器)。§7.2/§7.3 的早数字是在 -同一刻另有 worker 做全量 debug 构建(偏悲观的真实负载)时测的,§7.1 的 M96 两行是空载冷编译。 +构建环境:rustc 1.99.1 stable / `x86_64-pc-windows-gnu`(MinGW 链接器)。§7.2/§7.3 的早数字是另有 worker 并发做全量 debug 构建时测的(偏悲观);§7.1 的三行 M96 数据是空载冷编译。 ### 7.1 体积与冷构建耗时(实测字节数,不是估算) @@ -231,13 +232,12 @@ SLINT_BACKEND=winit-software unlucky-launcher # 环境变量(Slint | `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(冷) | -测量条件(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 依赖)。 +测量条件(M96 三行):12 核 / `x86_64-pc-windows-gnu` / `-j 4`,源码机械盘、`CARGO_TARGET_DIR` 在 SSD,均为**删空 target 后的 +冷编译**、同机同晚无并发负载;M46 两行是更早提交上的数字(依赖已热 / 另有并发),**跨行比耗时没意义**。 +① **依赖 opt-level 从 3 退回 `"z"` 又快 29.6% 又小 19~24%**(两臂同为 cu16,唯一自变量是 opt-level)——抬 opt-level 是拿编译时间和体积换运行期; +`cu1 → cu16` 的收益本轮**没有同条件对照**,未归因。② `fast` **冷编译比 `release` 还慢**(24m27s vs 16m01s)、体积 2.3 倍,瓶颈在两个大 UI +crate + 链接 + 机械盘 IO——定位是 **SSD 机器与增量重编**,不是冷编译加速。体积归因:GUI 相对旧基线 +12.9%(13.92 → 15.72 MiB)里 +「代码增长」与「依赖 cu16」**未分离**;`ull` 只 +1.7%,在 +1~3% 口径内。体积构成:Slint 运行时(双渲染器 + winit/accesskit/托盘)+ rustls/reqwest + rig/rmcp/axum 为主,`ull` 更小是 D18(CLI 零 UI 依赖)的结果。 ### 7.2 冒烟(真跑,2026-10-31 夜间 + 11-01 早,本机 Win11 26100)