跳转到主内容release、交叉编译和 binary size | 物联网民工

release、交叉编译和 binary size

C 项目交付前,通常不会只看“能编译”。

还要看:

是不是 release 优化
目标架构和 ABI 对不对
链接的是目标系统的库还是宿主机库
二进制有没有带多余符号
动态库依赖目标系统有没有
体积是否符合设备限制
崩溃后还能不能符号化

Rust 交付 CLI、系统库或 C/Rust 边界产物时,也要回答这些问题。Cargo 简化了构建命令,但没有取消交付检查。

release build 不是换个目录

开发时常用:

cargo build

交付时通常用:

cargo build --release

产物从:

target/debug/

变成:

target/release/

但区别不只是目录。release profile 会启用优化,编译更慢,运行更快。

可以在 Cargo.toml 配置:

[profile.release]
opt-level = 3
debug = "line-tables-only"
lto = "thin"
codegen-units = 1
panic = "abort"
strip = "symbols"

不要机械复制这段配置。每个选项都有代价:

  • opt-level 影响速度和体积
  • lto 可能减小体积或提升性能,但增加链接时间
  • codegen-units = 1 有利于优化,但编译更慢
  • panic = "abort" 可能减小体积,但不再展开栈
  • strip 减小二进制,但影响现场排障
  • debug 决定还能保留多少符号化信息

release 配置是交付策略,不是越激进越好。

binary size 先量,再调

不要凭感觉调体积。先看当前产物:

ls -lh target/release/device-cli
file target/release/device-cli

看 ELF 段大小:

size target/release/device-cli

看动态依赖:

ldd target/release/device-cli

看符号:

nm -S --size-sort target/release/device-cli

很多体积问题不是 Rust 特有的,而是普通链接问题:

  • 静态链接带进了大量库
  • 启用了不需要的 feature
  • debug 信息没有处理
  • panic/unwind 相关代码保留
  • LTO 没开,跨 crate 优化不足
  • 依赖里拉进了大型格式、TLS、日志或正则库

先定位来源,再决定调 profile、feature 还是依赖。

opt-level 不是永远越高越小

Rust release 默认常见优化目标是速度。小设备或 CLI 工具可能更关心体积。

可以试:

[profile.release]
opt-level = "z"

或者:

[profile.release]
opt-level = "s"

大致理解:

3   -> 偏速度
s   -> 偏体积
z   -> 更偏体积

但实际结果要测。某些程序 opt-level = 3 反而因为内联和优化效果更好,不一定比 z 差。不要用规则替代测量。

panic 策略影响体积和语义

默认 panic 可能使用 unwind。对库和 FFI 来说,panic 穿过 C ABI 本来就应该避免。

如果你的程序或库边界能接受 abort,可以设置:

[profile.release]
panic = "abort"

这通常能减少一些体积,也让 panic 行为更直接。

但要清楚代价:

panic 后直接终止进程
不会运行 unwind 路径上的析构
不适合需要 catch_unwind 恢复的场景

CLI 工具常常可以接受;嵌入已有宿主进程的动态库要更谨慎。动态库里 abort 可能把整个宿主进程带走。

strip 和 debug 信息要平衡

交付产物经常需要 strip:

[profile.release]
strip = "symbols"

或者构建后手工:

strip target/release/device-cli

但线上崩溃定位需要符号。常见做法是:

交付 stripped 二进制
构建服务器保存未 strip 的符号文件
记录版本、commit、build id
崩溃地址用对应符号文件还原

不要为了省几百 KB,把所有排障能力丢掉。设备现场问题最怕“二进制很小,但地址完全对不上源码”。

交叉编译先看 target triple

Rust 交叉编译通过 target triple 指定目标:

rustup target add aarch64-unknown-linux-gnu
cargo build --release --target aarch64-unknown-linux-gnu

target triple 里包含架构、系统和 ABI 线索:

x86_64-unknown-linux-gnu
aarch64-unknown-linux-gnu
armv7-unknown-linux-gnueabihf
x86_64-unknown-linux-musl

对 C 开发者来说,它对应的不是单纯“CPU 架构”,还包括 ABI 和 C 库生态。

gnu 通常意味着 glibc 生态动态链接;musl 常用于更独立的静态链接场景。具体选择要看目标系统。

交叉编译成功不等于能运行

把二进制放到目标设备前,先看:

file target/aarch64-unknown-linux-gnu/release/device-cli
readelf -h target/aarch64-unknown-linux-gnu/release/device-cli

确认:

ELF machine 是否匹配目标 CPU
32/64 位是否匹配
动态链接器路径是否存在
依赖的 .so 目标 rootfs 是否有
浮点 ABI 是否匹配
目标内核和 C 库版本是否支持

动态链接程序还要看:

readelf -d target/.../device-cli

如果目标板上能跑:

ldd ./device-cli

不能只看“编译通过”。交叉编译失败常在构建阶段暴露,交叉运行失败更多发生在 ABI、动态链接器、rootfs 和系统库版本上。

C 依赖要走目标 sysroot

前面讲过 build.rs 和 pkg-config。交叉编译时,C 依赖必须来自目标 sysroot。

错误模型:

build.rs 在 x86_64 host 运行
pkg-config 找到了 host /usr/lib 的库
最终要给 ARM 设备运行

正确方向:

Rust target 是 ARM
C 头文件和库来自 ARM sysroot
pkg-config 查的是 target .pc
链接器使用目标工具链

这类问题和 Rust 语言无关,是交叉编译基本功。Rust 只是把它放进 Cargo 流程里。

feature 会影响体积

体积大时,先看启用了哪些 feature。

例如一个 CLI 工具可能因为启用了默认 feature,带进 TLS、压缩、JSON、正则或大型日志栈。

依赖可以关闭默认 feature:

[dependencies]
some-crate = { version = "1", default-features = false, features = ["std"] }

但不要盲目关。关闭默认 feature 可能改变功能、平台支持或安全属性。

体积优化顺序通常是:

确认 release profile
检查依赖和 feature
看是否需要动态/静态链接
再调 LTO、strip、panic、opt-level
最后考虑替换重依赖或拆功能

库和 CLI 的 release 策略不同

CLI 工具通常可以更激进:

panic = abort
strip symbols
opt-level = z 或 s
单文件部署

给 C 调用的动态库要谨慎:

不要让 panic 直接 abort 宿主进程,除非契约写清楚
保留足够符号用于崩溃定位
保持 ABI 稳定
确认动态依赖在目标系统存在

同一个 workspace 里,不同产物可以有不同交付策略。不要把 CLI 的体积策略机械套到 FFI 动态库上。

这一篇先记住什么

Rust release 和交叉编译可以先这样看:

cargo build --release          -> 使用 release profile
[profile.release]              -> 优化、LTO、strip、panic 策略
--target <triple>               -> 指定目标架构/系统/ABI
file/readelf                    -> 检查 ELF、架构、解释器、动态段
ldd                             -> 检查目标运行时动态依赖
size/nm                         -> 看段大小和大符号
default-features = false        -> 控制依赖默认能力
strip/debug/build id            -> 体积和排障能力的平衡

交付前不要只问“能不能编译”。更实用的问题是:这个二进制是不是为正确目标构建,依赖能不能在目标系统找到,体积从哪里来,现场崩溃还能不能定位。

下一篇讲发布 CLI、发布 crate 和版本管理。release build 解决单个产物怎样做出来;发布策略要回答它怎样被别人安装、升级和长期兼容。