安卓动态链接库文件体积优化

上篇文章 中我们实践了使用rust编译安卓端的动态连接库的开发。随之而来的问题也比较明显,就是相比C/C++开发的.so库来说,rust开发的库体积都偏大,甚至大出了10倍以上,本文就体积问题进行优化探索。

调整优化等级

默认的编译优化等级是O3,该优化的目的提高代码的运行速度,但是与此同时会对部分循环进行展开,体积造成膨胀。在此我们以缩减体积为目标,将优化选项改为z,表示生成最小二进制体积:

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

即便开启优化后,体积变化并不明显,因此我们还需要进一步优化。

开启LTO

LTO(Link Time Optimization)可以在链接时消除冗余代码,减小二进制体积——代价是更长的链接时间。

[profile.release]
opt-level = 'z'
lto = true

Panic立刻终止

rust默认的panic会在崩溃时进行栈回溯,方便定位问题。然而会带来额外的体积增加,将这一功能使用abort替代。

[profile.release]
opt-level = 'z'
lto = true
panic = 'abort'

到目前为止,常规的优化手段已经用完了,后续优化需要配合一些代码的额外变动。
接下来我们使用rust分析工具bloat对产物进行分析 cargo install cargo-bloat
cargo bloat --target aarch64-linux-android --release 分析结果如下:

 File  .text     Size     Crate Name
 0.8%   6.3%  12.3KiB       std std::backtrace_rs::symbolize::gimli::resolve::{{closure}}
 0.7%   6.0%  11.8KiB       std std::backtrace_rs::symbolize::gimli::Context::new
 0.6%   4.7%   9.2KiB       std addr2line::ResUnit<R>::find_function_or_location::{{closure}}
 0.5%   4.2%   8.1KiB       std miniz_oxide::inflate::core::decompress
 0.4%   3.3%   6.5KiB       std gimli::read::dwarf::Unit<R>::new
 0.4%   3.0%   5.9KiB       std addr2line::Lines::parse
 0.3%   2.3%   4.5KiB       std gimli::read::unit::parse_attribute
 0.2%   1.7%   3.4KiB       std addr2line::function::Function<R>::parse_children
 0.2%   1.7%   3.3KiB       std gimli::read::abbrev::Abbreviations::insert
 0.2%   1.6%   3.1KiB       std gimli::read::rnglists::RngListIter<R>::next
 0.2%   1.5%   2.9KiB       std <&T as core::fmt::Display>::fmt
 0.2%   1.4%   2.8KiB [Unknown] _ZN9libunwind10CFI_ParserINS_17LocalAddressSpaceEE20parseFDEInstructionsERS1_RKNS2_8FDE_InfoERKNS2_8CIE_InfoEmiPNS2_10PrologInfoE
 0.2%   1.4%   2.8KiB       std rustc_demangle::try_demangle
 0.2%   1.4%   2.7KiB       std gimli::read::line::parse_attribute
 0.2%   1.3%   2.5KiB       std gimli::read::index::UnitIndex<R>::parse
 0.1%   1.2%   2.4KiB [Unknown] _ZN9libunwind17DwarfInstructionsINS_17LocalAddressSpaceENS_15Registers_arm64EE18evaluateExpressionEmRS1_RKS2_m
 0.1%   1.1%   2.1KiB       std core::slice::sort::recurse
 0.1%   1.1%   2.1KiB       std gimli::read::abbrev::DebugAbbrev<R>::abbreviations
 0.1%   1.0%   1.9KiB [Unknown] Java_com_mona_HelloWorld_hello
 0.1%   0.9%   1.8KiB       std core::str::pattern::StrSearcher::new
 6.3%  52.9% 103.7KiB           And 407 smaller methods. Use -n N to show more.
12.0% 100.0% 196.1KiB           .text section size, the file size is 1.6MiB

我们的 HelloWorld 实际上只占用了1.9k,占比只有1.0%,因此接下来就需要提高我们代码的占比。

移除一些无用字符串

在引入的第三方依赖里,开发者自己添加了很多字符串信息,大部分是用来完善提供运行时报错信息。通过修改、精简这些依赖库,删除无用代码,又可以省出一部分空间来。
同时,上面的优化尽管使用abort替代了panic,rust编译器仍然会生出一些格式化的字符串,使用panic_immediate_abort这个编译选项禁用这个行为。
在这一步中,编译命令需要使用到 nightly 的相关功能,因此我们还需要安装 rust的nightly版本。

  1. 安装nightly版本
rustup install nightly
  1. 切换到nightly版本的cargo
rustup default nightly
  1. 检查是否切换成功
rustc --version 

提示出rustc 1.78.0-nightly (5119208fd 2024-03-02)就切换成功了,如果提示需要安装nightly的部分依赖则根据提示执行命令就行。
切换回发布版命令:rustup default stable
之后我们使用cargo +nightly build -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort --target aarch64-linux-android --target armv7-linux-androideabi --release命令来编译即可。
使用rust分析工具bloat对产物进行分析,如下:

 File  .text   Size     Crate Name
 8.8%  30.0% 1.9KiB [Unknown] Java_com_mona_HelloWorld_hello
 2.8%   9.6%   624B     cesu8 cesu8::to_java_cesu8
 2.2%   7.4%   484B      core <core::str::lossy::Utf8Chunks as core::iter::traits::iterator::Iterator>::next
 1.6%   5.5%   360B      core core::fmt::write
 1.6%   5.5%   356B      core core::fmt::Formatter::pad
 1.5%   5.3%   344B      core <core::str::iter::Chars as core::iter::traits::iterator::Iterator>::count
 1.1%   3.7%   244B    alloc? <alloc::string::String as core::fmt::Write>::write_char
 1.0%   3.3%   216B     alloc alloc::ffi::c_str::CString::from_vec_unchecked
 0.8%   2.6%   168B      core <core::str::iter::CharIndices as core::iter::traits::iterator::Iterator>::next
 0.7%   2.3%   152B      core core::fmt::Formatter::padding
 0.6%   2.1%   140B     alloc alloc::raw_vec::RawVec<T,A>::grow_amortized
 0.6%   2.0%   128B     alloc alloc::raw_vec::finish_grow
 0.5%   1.8%   116B [Unknown] __rust_realloc
 0.5%   1.7%   108B       jni alloc::borrow::Cow<B>::into_owned
 0.4%   1.5%   100B      core core::fmt::PostPadding::write
 0.4%   1.2%    80B     cesu8 alloc::vec::Vec<T,A>::extend_trusted
 0.3%   1.2%    76B     alloc alloc::vec::Vec<T,A>::push
 0.3%   1.2%    76B     alloc alloc::vec::Vec<T,A>::extend_from_slice
 0.3%   1.1%    72B      core core::fmt::getcount
 0.3%   1.0%    64B     alloc alloc::alloc::Global::alloc_impl
 3.0%  10.1%   660B           And 30 smaller methods. Use -n N to show more.
29.2% 100.0% 6.4KiB           .text section size, the file size is 21.8KiB

发现我们自己的代码已经占比达到30%了,并且整个包体积也只有22KB。
到这里我们已经从1.7MB优化到了22KB,初步达到我们的预期。

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×