Rust二进制文件大小优化,在优化二进制文件过程中,编译时间会增加,但是一般编译时候的优化会加快程序执行速度

初始化项目

Ubuntu22上面进行实验

创建项目

$ cargo new demo

修改Cargo.toml如下,需要加入一点常用的库

[dependencies]
tokio = {version = "1", features = ["full"]}
tracing = "0.1"
tracing-subscriber = "0.3"

修改main.rs如下

#[tokio::main]
async fn main() {
    tracing_subscriber::fmt::init();
    loop {
        tokio::time::sleep(std::time::Duration::from_secs(1)).await;
        tracing::info!("Hello, world!");
    }
}

debugrelease的大小对比

编译

$ cargo build
$ cargo build -r

查看大小

$ ll target/*           
target/debug:
总计 28M
-rwxrwxr-x  2 gong gong  28M 四月   25 15:47 demo
...

target/release:
总计 5.0M
-rwxrwxr-x  2 gong gong 5.0M 四月   25 15:46 demo
....

可见release版本二进制大约是debug版本的17%,后续所有二进制优化采用release 5M大小为基准,每次优化都只改动一个配置项作为对比

优化符号信息symbols

LinuxmacOS上,默认情况下,符号信息包含在编译的.elf文件中,正确执行二进制文件不需要此信息

配置Cross.toml

[profile.release]
strip = true 

编译之后demo二进制变为887k,约为之前的17%,效果好

这个配置项在项目比较大的时候,比如最终二进制是100M优化效果就不怎么明显了,一般符号信息是会减小几M大小,存在一个上限

配置优化level

opt-level可选项

  • 0: 无优化(debug默认优化)
  • 1: 基本优化
  • 2: 一些优化
  • 3: 全部优化,(release默认优化)
  • "s": 优化输出的二进制文件的大小
  • "z": 优化二进制文件大小,但也会关闭循环向量化

配置如下

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

优化后demo变为5.1M了,二进制反而变大,很多文档都提到要配置这个优化项,但是从来没发现有什么效果

详情参考官方文档

https://doc.rust-lang.org/cargo/reference/profiles.html

启用链接时优化

默认情况下,Cargo指示编译单元单独编译和优化,LTO指示链接器在链接阶段进行优化,例如,这可以删除无用代码,有些代码段不会被调用到,并减少二进制大小

默认release是没有开启这个配置,开启这个配置后如果项目比较大,则会大幅度增加编译时间,项目依赖越多,代码总量越多,优化效果越明显

支持的选项包括:

  • false: 只会对代码生成单元中的本地包进行 thin LTO 优化,若代码生成单元数为 1 或者 opt-level 为 0,则不会进行任何 LTO 优化
  • truefat:对依赖图中的所有包进行 fat LTO 优化
  • thin:对依赖图的所有包进行 thin LTO,相比 fat 来说,它仅牺牲了一点性能,但是换来了链接时间的可观减少
  • off: 禁用 LTO
[profile.release]
lto = true

编译后二进制变为2.6M,约为之前的52%,效果好

减少并行代码生成单元

Cargo为发布版本指定了16个并行代码单元,这改善了编译时间,但妨碍了一些优化,如果配置为1,就可以最大程度避免并行代码单元中的妨碍优化

[profile.release]
codegen-units = 1

编译后文件大小为4.8M,约为之前的96%,效果不怎么明显

配置程序panic时候的行为

Rust代码遇到必须调用panic!()的情况时,它会展开堆栈并生成有用的回溯,展开代码需要额外的二进制,可以调整Rust立即中止程序而不是展开栈信息,这样就不需要额外的展开代码

[profile.release]
panic = "abort"

编译后程序大小为4.7M,约为之前的97.5%,这样配置的话程序如果报错就看不到什么地方panic了,会比较影响线上环境体验,不推荐使用

大杀器UPX

upx是一个二进制压缩工具,支持各类二进制可执行文件的压缩,rust/golang/c.....,在大部分情况下可以把二进制执行文件压缩到原来的30%左右,压缩后的文件可以直接由系统执行,支持多系统和平台

项目地址

安装的时候尽量去官方项目的release获取到最新的release,里面包含一个upx可执行文件用于压缩二进制程序

https://github.com/upx/upx

upx支持不同的压缩级别1-9,最高压缩级别是9

一般执行upx -9 文件即可

$ upx -9 target/release/demo
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2023
UPX 4.0.2       Markus Oberhumer, Laszlo Molnar & John Reiser   Jan 30th 2023

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
   5178056 ->   1173024   22.65%   linux/amd64   demo                          

Packed 1 file.

之后查看二进制可执行文件变为了1.2M,约为原来的22.6%

linux上面使用strings查看二进制文件

查看压缩前的可执行文件内容

$ strings target/release/demo |head -n 10
/lib64/ld-linux-x86-64.so.2
_ITM_deregisterTMCloneTable
__gmon_start__
_ITM_registerTMCloneTable
_Unwind_Resume
_Unwind_Backtrace
_Unwind_GetLanguageSpecificData
_Unwind_GetIPInfo
_Unwind_GetDataRelBase
_Unwind_GetRegionStart

查看压缩后的内容,可以看到UPX把开头的信息改写了

$ strings target/release/demo |head -n 10
1tUPX!
m@/H
(O0OS
tdPo
/lib64
nux-x86-
.so.
_ITM_deregist
CloneTable
__gm_.art

原理

采用二进制程序加壳技术,UPX 将程序压缩,并在头部加入解压的程序,加壳过的程序可以直接运行,但是不能查看源代码,要经过脱壳才可以查看源代码

加壳与解压

  • 在文件头里加了一段指令,告诉CPU,怎么才能解压自己(在解压时候执行)
  • 利用特殊的算法,对EXEDLL,其他二进制文件里的资源进行压缩,类似zip的效果
  • 压缩之后的文件,可以独立运行,给可执行的文件加上个外衣
  • 加壳工具解压过程完全隐蔽,都在内存中完成,用户执行的只是这个外壳程序
  • 执行时候,壳就会把原来的程序在内存中解开,解开后,以后的就交给真正的程序

优点

  • UPX 可以压缩各种类型的可执行文件,压缩效率非常可观
  • 压缩后的文件可以直接由操作系统执行
  • 压缩过程不会修改源文件,也就意味着解压后直接可以得到原始文件
  • 不会产生额外的动态库调用
  • 没有运行时性能损失
  • 反跟踪、被人跟踪调试、防止程序被别人静态分析,保护你程序数据的完整性,防止被程序修改和被窥视内幕

缺点

  • 运行的程序不会共享数据段(汇编),所以多实例运行的程序不适合压缩
  • 使用 lddsize 命令无法获取到程序的有效信息
  • 打开时消耗更多的 CPU 资源(执行的时候会使用解压算法对压缩后的可执行文件进行解压运算)
  • 在运行时占用更多的内存(主要是多了几兆UPX解压程序)

总结

很多优化方式一般都是压缩效果不怎么明显,但是会增加编译耗时或者影响程序使用体验

所以采用优化方式如下

  • 启用链接时优化
  • 除去不必要的Symbols
  • 采用upx进一步压缩

配置Cargo.toml为如下

[profile.release]
strip = true
lto = true

参考阅读

Minimizing Rust Binary Size

使用 UPX 压缩可执行文件

How They Work, Featuring UPX


龚正阳
29 声望5 粉丝

粗犷型程序员