1 二进制小型化优化方案
适用范围:C / C++ / Rust / Go / Swift / Android / iOS / 嵌入式固件等场景
1.1 概述与基础概念
1.1.1 为什么要做二进制小型化?
| 场景 | 主要收益 |
|---|---|
| 移动端 App | 降低用户下载门槛、减少卸载率、节省流量 |
| 嵌入式/IoT | Flash/ROM 容量受限,直接决定能否运行 |
| 服务端微服务 | 加速容器镜像拉取、缩短冷启动时间 |
| WebAssembly | 减少网络传输、提升首屏加载速度 |
| 边缘计算 | 硬件算力/存储双受限 |
1.1.2 二进制的组成结构(ELF 为例)
ELF Binary
├── .text ← 可执行代码段(通常最大)
├── .rodata ← 只读数据(字符串常量、查找表)
├── .data ← 已初始化全局/静态变量
├── .bss ← 未初始化全局/静态变量(不占文件体积)
├── .debug_* ← 调试信息(DWARF)
├── .symtab ← 符号表
├── .dynsym ← 动态符号表
└── ...
1.1.3 衡量指标
- 文件大小(File Size):磁盘/下载占用,用户感知最直接
- 安装大小(Installed Size):解压/安装后的占用,iOS/Android 商店展示
- 内存映像大小(VM Size):运行时进程占用,影响 OOM 风险
- 下载大小(Download Size):经过 gzip/brotli 压缩后,实际网络传输量
1.2 编译器层面优化
1.2.1 优化等级选择
1.2.1.1 GCC / Clang
bash
-O0 # 不优化(调试用)
-O1 # 基础优化,略微缩小代码
-O2 # 标准优化(速度优先)
-O3 # 激进优化(可能膨胀二进制)
-Os # 针对尺寸优化(= O2 去掉会膨胀体积的 pass)
-Oz # 比 Os 更激进的尺寸优化(Clang 独有,速度换体积)
-Og # 调试友好优化
经验法则 :大多数尺寸敏感项目首选
-Os;若能接受性能下降,Clang-Oz可额外节省 5%~15%。
1.2.1.2 MSVC(Windows)
/O1 # 最小代码优化
/O2 # 最快速度(体积可能更大)
/Os # 速度与大小之间倾向大小
/GL # 全程序优化(配合 /LTCG 使用)
1.2.1.3 Rust
toml
# Cargo.toml
[profile.release]
opt-level = "z" # 最小体积("s" 次之)
lto = true
codegen-units = 1
panic = "abort"
strip = true
1.2.2 链接时优化(LTO / LTCG)
LTO 允许编译器跨编译单元进行分析,消除冗余代码。
bash
# GCC Full LTO
gcc -flto -O2 -o output main.c utils.c
# Clang ThinLTO(并行,构建更快)
clang -flto=thin -O2 -o output main.c utils.c
# Rust
[profile.release]
lto = "thin" # 或 lto = true(fat LTO,更慢更彻底)
⚠️ LTO 会显著增加链接时间和内存消耗,CI 机器需评估资源。
1.2.3 函数/数据节(Section)粒度控制
bash
# GCC/Clang:每个函数/变量单独放入一个节,便于链接器裁剪
-ffunction-sections
-fdata-sections
配合链接器 --gc-sections 使用,可删除所有未引用的节。
1.2.4 Profile-Guided Optimization(PGO)
PGO 让编译器根据实际运行数据重排热路径,使 CPU 指令缓存命中更好,同时可将冷路径代码移至独立节,便于剔除。
bash
# Step 1:插桩编译
clang -fprofile-instr-generate -o app_instrumented main.c
# Step 2:运行典型场景,收集 profraw
./app_instrumented
llvm-profdata merge -output=app.profdata *.profraw
# Step 3:使用 profile 重新编译
clang -fprofile-instr-use=app.profdata -O2 -o app main.c
1.2.5 其他编译选项
bash
# 不生成异常处理表(若项目不使用 C++ 异常)
-fno-exceptions
# 不生成 RTTI(若不使用 dynamic_cast / typeid)
-fno-rtti
# 不展开循环(减少代码膨胀)
-fno-unroll-loops
# 内联函数阈值(减小可降低代码膨胀)
-finline-limit=10
# 禁用栈保护(嵌入式等安全不敏感场景)
-fno-stack-protector
# 使用短枚举(嵌入式)
-fshort-enums
# 合并重复常量字符串
-fmerge-all-constants
1.2.6 指令集选择
在嵌入式/ARM 场景中,指令集直接影响代码密度:
bash
# ARM Thumb2 指令集(16/32 位混合,比 ARM 32 位更紧凑)
-mthumb
# RISC-V:使用压缩指令集扩展(RVC)
-march=rv32imc
1.3 链接器层面优化
1.3.1 Dead Code Elimination(DCE)
bash
# GNU ld / gold
-Wl,--gc-sections
# macOS ld
-Wl,-dead_strip
# lld(LLVM)
-Wl,--gc-sections
必须配合编译器的
-ffunction-sections -fdata-sections使用,否则无效。
1.3.2 选择高效链接器
| 链接器 | 特点 |
|---|---|
ld(GNU BFD) |
最广泛,但慢,优化能力一般 |
gold |
比 BFD 快,支持 LTO |
lld(LLVM) |
最快,LTO/ThinLTO 支持最好,推荐首选 |
mold |
极速(并行链接),但尺寸优化功能与 lld 相当 |
bash
# 切换到 lld
-fuse-ld=lld
1.3.3 版本脚本(Version Script)
控制导出的符号,隐藏不必要的符号可减小 .dynsym 表。
ld
# version.map
{
global:
my_public_api; # 仅导出这个
local:
*; # 其余全部隐藏
};
bash
gcc -Wl,--version-script=version.map -o libfoo.so foo.c
1.3.4 符号可见性
c
// 方式1:GCC 属性
__attribute__((visibility("hidden"))) void internal_func() {}
__attribute__((visibility("default"))) void public_api() {}
// 方式2:编译器全局默认隐藏
// -fvisibility=hidden
// 再对需要导出的符号单独标记 default
1.3.5 ICF(Identical Code Folding)
将内容完全相同的函数合并为一个,可显著缩减模板/泛型代码膨胀。
bash
# lld
-Wl,--icf=all
# gold
-Wl,--icf=safe
# MSVC
/OPT:ICF
⚠️
--icf=all可能破坏以函数地址作为唯一标识的代码,使用前需测试。
1.3.6 去除 PLT(Procedure Linkage Table)
bash
# 对于已知位置的符号,直接调用而非通过 PLT
-Wl,-z,now # 启动时立即解析所有符号(RELRO)
-fno-plt # 直接调用,跳过 PLT
1.4 代码层面优化
1.4.1 避免模板过度实例化(C++)
cpp
// ❌ 每个 T 都生成一套代码
template<typename T>
void process(T* ptr, size_t n) { /* 大量逻辑 */ }
// ✅ 类型无关逻辑抽到非模板函数
void process_impl(void* ptr, size_t elem_size, size_t n);
template<typename T>
inline void process(T* ptr, size_t n) {
process_impl(ptr, sizeof(T), n); // 薄包装
}
1.4.2 避免虚函数滥用(C++)
虚函数会强制生成 vtable 和 RTTI,且难以被内联/DCE。
cpp
// 替代方案:CRTP(Curiously Recurring Template Pattern)
template<typename Derived>
class Base {
public:
void interface() {
static_cast<Derived*>(this)->impl();
}
};
class Concrete : public Base<Concrete> {
public:
void impl() { /* 实际逻辑 */ }
};
1.4.3 字符串常量优化
c
// ❌ 重复字符串面量导致体积膨胀
const char* a = "hello world";
const char* b = "hello world"; // 可能生成两份
// ✅ 集中管理字符串
// 使用 -fmerge-all-constants 让编译器合并
// 或自定义字符串池
// 嵌入式:压缩字符串后运行时解压
// 或使用 printf 格式字符串代替多个字面量
1.4.4 使用 [[nodiscard]] 与 inline 控制膨胀(C++17)
cpp
// 小型高频函数内联,避免函数调用开销同时不增加代码段
inline int clamp(int x, int lo, int hi) {
return x < lo ? lo : x > hi ? hi : x;
}
// 大型函数明确禁止内联
__attribute__((noinline)) void heavy_init();
1.4.5 减少全局对象与静态初始化
全局 C++ 对象的构造函数会生成 .init_array 段,增加启动代码。
cpp
// ❌ 全局对象
static std::vector<int> g_cache; // 有构造函数
// ✅ 延迟初始化 / 使用 POD
static int g_cache[MAX_SIZE]; // POD,无构造函数
static int g_cache_size = 0;
// 或使用 constinit / constexpr(C++20)
constinit static Config g_config = {};
1.4.6 枚举与类型大小收紧
c
// 使用尽量小的整数类型
uint8_t status; // 代替 int
uint16_t count; // 代替 size_t(嵌入式场景)
// 位域
struct Flags {
uint8_t enabled : 1;
uint8_t mode : 3;
uint8_t level : 4;
};
1.4.7 避免异常与 RTTI(C++)
cpp
// 编译时关闭异常(-fno-exceptions)后,
// 用错误码或 std::expected 替代
std::expected<Result, Error> compute() {
if (failed) return std::unexpected(Error::NotFound);
return result;
}
// 或使用 outcome / tl::expected 等库
1.4.8 Rust 特有技巧
rust
// 1. 使用 panic = "abort" 避免 panic unwind 代码
// 2. 避免动态分配(no_std 场景)
#![no_std]
#![no_main]
// 3. 减少 monomorphization:用 dyn Trait 代替泛型(用运行时成本换体积)
fn process(handler: &dyn Handler) {} // 生成一份代码
fn process<H: Handler>(handler: &H) {} // 每个 H 生成一份
// 4. 使用 #[inline(never)] 阻止过度内联
#[inline(never)]
fn heavy_function() {}
// 5. 裁剪 std 功能
// 在 Cargo.toml 中禁用默认 features
[dependencies]
serde = { version = "1", default-features = false, features = ["derive"] }
1.5 资源与数据优化
1.5.1 资源压缩
| 资源类型 | 优化方案 |
|---|---|
| PNG 图片 | pngquant(有损压缩)、optipng/zopflipng(无损) |
| JPEG | mozjpeg、jpegoptim |
| WebP | 比 PNG/JPEG 更小,建议迁移 |
| SVG | svgo 去除冗余属性 |
| 音频 | opus/aac 代替 mp3,适当降低比特率 |
| 视频 | AV1/HEVC,或按需流式加载 |
| 字体 | 子集化(subsetting)、woff2 格式、pyftsubset |
1.5.2 查找表(LUT)替代运算
c
// 计算成本换体积(运行时计算 vs 静态表)
// 三角函数表、CRC 表等可预计算后编译进二进制
// 嵌入式中也可在运行时生成(RAM 换 Flash)
// 或使用 Q 格式定点数代替浮点运算,减少浮点库依赖
1.5.3 嵌入资源到二进制
bash
# GNU ld objcopy
objcopy -I binary -O elf32-littlearm logo.png logo.o
# 生成 _binary_logo_png_start / _end / _size 符号
# CMake + xxd
xxd -i resource.bin > resource.h
1.5.4 按需加载资源
避免将所有资源打包进二进制,改为运行时按需从网络/文件系统加载:
- Android:AAB(App Bundle)按设备配置分发资源
- iOS:On-Demand Resources(ODR)
- PC/服务端:DLC、资源热更新
1.6 依赖库管理
1.6.1 静态链接 vs 动态链接
| 维度 | 静态链接 | 动态链接 |
|---|---|---|
| 单文件体积 | 更大(含库代码) | 更小(依赖系统库) |
| 安装总体积 | 多应用共享时更大 | 共享库只有一份 |
| DCE 效果 | 好(链接器可裁剪) | 差(整个 .so 必须保留) |
| 部署灵活性 | 好(无依赖) | 需确保库版本兼容 |
结论 :若目标是单二进制小型化,优先静态链接 +
--gc-sections;若目标是系统总体积,使用动态链接共享系统库。
1.6.2 替换重量级依赖
| 场景 | 重型库 | 轻量替代 |
|---|---|---|
| JSON 解析 | RapidJSON(功能全) | jsmn、yyjson |
| HTTP 客户端 | libcurl | picohttp、llhttp |
| 正则表达式 | PCRE2 | re2(精简版)、tiny-regex |
| 日志 | log4cpp | spdlog(header-only)、NanoLog |
| 压缩 | zlib | miniz(单文件)、lz4(更快更小) |
| TLS | OpenSSL | mbedTLS、BearSSL(嵌入式友好) |
| C++ 标准库 | libstdc++ | libc++(通常更小)、musl libc |
1.6.3 使用 --as-needed 链接标志
bash
# 只链接实际用到的动态库,不引入无用的 .so 依赖
-Wl,--as-needed
1.6.4 Rust:cargo-bloat 分析依赖贡献
bash
cargo install cargo-bloat
cargo bloat --release --crates # 按 crate 显示体积贡献
cargo bloat --release -n 20 # 显示最大的 20 个函数
1.6.5 Go:按需 build tags 裁剪
go
//go:build !windows
bash
# 排除 CGO
CGO_ENABLED=0 go build -o app .
# 仅使用纯 Go 实现的网络库
go build -tags netgo -o app .
1.7 调试信息处理
1.7.1 Strip 符号表
bash
# 完全剥离(生产发布)
strip --strip-all output
# 仅剥离调试信息,保留符号表(可用于符号化崩溃)
strip --strip-debug output
# macOS
strip -x output # 删除局部符号
strip -S output # 删除调试符号
# objcopy 方式(可同时保留带调试的副本)
objcopy --only-keep-debug output output.dbg # 提取调试信息
objcopy --strip-debug output # 剥离原文件
objcopy --add-gnu-debuglink=output.dbg output # 添加链接
1.7.2 Split DWARF(DWP)
将 DWARF 调试信息拆分到独立 .dwo 文件,发布时不随二进制分发。
bash
# GCC/Clang
-gsplit-dwarf
# 打包所有 .dwo 为一个文件
llvm-dwp -e output -o output.dwp
1.7.3 压缩调试信息(开发阶段)
bash
# 压缩 DWARF 节,减少 debug 构建体积(不影响 release)
-gz=zlib # GCC/Clang 支持
1.7.4 最小化发布构建
makefile
RELEASE_FLAGS := -O2 -DNDEBUG -ffunction-sections -fdata-sections \
-fno-exceptions -fno-rtti -fvisibility=hidden
RELEASE_LDFLAGS := -Wl,--gc-sections -Wl,--strip-all \
-Wl,--icf=all -flto
1.8 平台专项优化
1.8.1 Android
1.8.1.1 R8 / ProGuard(Java/Kotlin)
groovy
// build.gradle
android {
buildTypes {
release {
minifyEnabled true // 开启代码压缩
shrinkResources true // 开启资源压缩
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
'proguard-rules.pro'
}
}
}
1.8.1.2 App Bundle(AAB)
bash
# AAB 按设备 ABI / 语言 / 屏幕密度分发,避免打包所有资源
./gradlew bundleRelease
1.8.1.3 NDK 层
cmake
# CMakeLists.txt
target_compile_options(mylib PRIVATE -Os -ffunction-sections -fdata-sections)
target_link_options(mylib PRIVATE -Wl,--gc-sections -Wl,--icf=safe)
set_target_properties(mylib PROPERTIES LINK_FLAGS "-s") # strip
1.8.1.4 ABI 过滤
groovy
android {
defaultConfig {
ndk {
// 仅支持主流 ABI,减少包体积
abiFilters 'arm64-v8a', 'x86_64'
}
}
}
1.8.2 iOS
1.8.2.1 编译器优化
// Xcode Build Settings
SWIFT_OPTIMIZATION_LEVEL = -Osize // Swift
GCC_OPTIMIZATION_LEVEL = s // ObjC/C
DEAD_CODE_STRIPPING = YES
ENABLE_BITCODE = NO // Bitcode 已废弃,不再需要
STRIP_INSTALLED_PRODUCT = YES
STRIP_STYLE = all
1.8.2.2 Swift 特定
swift
// 使用 @inlinable 谨慎内联,避免不必要的模块间代码复制
@inlinable public func hot_path() { ... }
// 使用 final 阻止动态派发,便于优化
final class MyService { ... }
// 开启 Whole Module Optimization
// SWIFT_WHOLE_MODULE_OPTIMIZATION = YES
1.8.2.3 On-Demand Resources
将大型资源(关卡数据、字体等)声明为 ODR,下载时才获取:
swift
let request = NSBundleResourceRequest(tags: ["level-5"])
request.beginAccessingResources { error in ... }
1.8.3 WebAssembly
bash
# Emscripten
emcc -Os -flto --closure 1 \
-s FILESYSTEM=0 \
-s ASSERTIONS=0 \
-s MALLOC=emmalloc \ # 更小的 malloc 实现
-o app.wasm main.c
# wasm-opt(Binaryen 后处理,额外优化)
wasm-opt -Oz --enable-mutable-globals app.wasm -o app.opt.wasm
# Rust + wasm-pack
wasm-pack build --release
wasm-opt -Oz pkg/app_bg.wasm -o pkg/app_bg.opt.wasm
1.8.4 嵌入式 / 裸机(Bare Metal)
c
// 1. 去除 C 标准库,使用 newlib-nano 或自定义 syscall
// 链接 newlib-nano(ARM)
// --specs=nano.specs --specs=nosys.specs
// 2. 去除浮点支持(软浮点 + 不使用 libm)
-mfloat-abi=soft -mfpu=none
// 3. 自定义 startup 代码,去除未使用的初始化
// 替换 crt0.o 中的 __libc_init_array
// 4. 使用链接脚本精确控制段布局
// 将热路径函数放入 ITCM(指令紧耦合内存)
__attribute__((section(".itcm_text")))
void hot_isr_handler() {}
1.8.5 Go
bash
# 禁用 CGO(纯 Go 二进制更小,无需 glibc)
CGO_ENABLED=0 go build -o app .
# 去除调试信息和符号表
go build -ldflags="-s -w" -o app .
# UPX 压缩(运行时自解压,冷启动有延迟)
upx --best app
# 使用 trimpath 去除源码路径(减小符号信息)
go build -trimpath -o app .
# goblin:实验性的 Go binary 小型化工具
# github.com/nicholasgasior/goblin
1.9 分析与度量工具
1.9.1 符号大小分析
bash
# nm:列出所有符号及大小
nm --print-size --size-sort --radix=d output | tail -30
# objdump:反汇编 + 节大小
objdump -h output # 各节大小
objdump -t output | sort -k5 # 符号排序
# size:快速查看各段大小
size output
size -A output # 详细模式
1.9.2 专用分析工具
1.9.2.1 Bloaty McBloatface(强烈推荐)
bash
# 安装
git clone https://github.com/google/bloaty && cd bloaty
cmake -B build && cmake --build build
# 基础分析
bloaty output
# 按编译单元分解
bloaty output -d compileunits
# 对比两个版本的差异(关键!用于 CI 中检测体积回归)
bloaty new_binary -- old_binary
# 输出 CSV 供进一步处理
bloaty output -d symbols --csv > symbols.csv
1.9.2.2 cargo-bloat(Rust)
bash
cargo bloat --release --crates -n 20
1.9.2.3 Android 专用
bash
# apkanalyzer(Android SDK 自带)
apkanalyzer apk summary app-release.apk
apkanalyzer dex packages app-release.apk
apkanalyzer manifest print app-release.apk
1.9.2.4 iOS 专用
bash
# Xcode 内置 App Size Report
# Product → Archive → Distribute App → 选择 Development → 查看 App Thinning Size Report
# otool
otool -l app.o | grep -A4 "sectname"
1.9.3 可视化工具
| 工具 | 平台 | 功能 |
|---|---|---|
| Bloaty | 通用 | 层次化树形分析 |
| Binary Size Analyzer | ELF | 交互式 Web 可视化 |
| cargo-bloat | Rust | crate/函数级分析 |
| Twiggy | Wasm/ELF | 调用图分析,找出"保活"根因 |
| Weigh | Rust | 类型大小分析 |
| Android Profiler | Android | APK 内容树形图 |
| Xcode Memory Graph | iOS | 对象占用分析 |
1.9.4 段大小追踪脚本示例
python
#!/usr/bin/env python3
# track_size.py ------ 记录每次构建的二进制大小,便于趋势分析
import subprocess, json, datetime, pathlib, os
def get_section_sizes(binary):
out = subprocess.check_output(["size", "-A", binary]).decode()
sizes = {}
for line in out.splitlines()[1:]:
parts = line.split()
if len(parts) >= 2:
sizes[parts[0]] = int(parts[1])
return sizes
binary = "./build/app"
sizes = get_section_sizes(binary)
record = {
"timestamp": datetime.datetime.utcnow().isoformat(),
"commit": os.getenv("GIT_COMMIT", "local"),
"total_file": pathlib.Path(binary).stat().st_size,
"sections": sizes,
}
log = pathlib.Path("size_history.jsonl")
with log.open("a") as f:
f.write(json.dumps(record) + "\n")
print(json.dumps(record, indent=2))
1.10 CI/CD 集成
1.10.1 体积预算(Size Budget)
在 CI 中设置阈值,超标则 fail build:
yaml
# .github/workflows/size_check.yml
- name: Check binary size
run: |
SIZE=$(stat -c%s build/app)
MAX=5242880 # 5 MB
if [ "$SIZE" -gt "$MAX" ]; then
echo "❌ Binary too large: ${SIZE} bytes (limit: ${MAX})"
exit 1
fi
echo "✅ Binary size OK: ${SIZE} bytes"
1.10.2 PR 体积对比(Bloaty + GitHub Actions)
yaml
- name: Compare binary size with main
run: |
git fetch origin main
git checkout origin/main -- build/app
mv build/app build/app_main
git checkout HEAD -- build/app
bloaty build/app -- build/app_main \
--domain=vm -d sections 2>&1 | tee size_diff.txt
cat size_diff.txt >> $GITHUB_STEP_SUMMARY
1.10.3 自动生成 Size Report
yaml
- name: Generate size report
run: |
{
echo "## 📦 Binary Size Report"
echo "\`\`\`"
bloaty build/app -d compileunits -n 20
echo "\`\`\`"
} >> $GITHUB_STEP_SUMMARY
1.11 各语言速查表
1.11.1 C / C++
bash
# 编译
CFLAGS = -Os -ffunction-sections -fdata-sections \
-fno-exceptions -fno-rtti -fvisibility=hidden \
-flto
# 链接
LDFLAGS = -Wl,--gc-sections -Wl,--icf=all \
-Wl,--strip-all -fuse-ld=lld -flto \
-Wl,--as-needed
1.11.2 Rust
toml
# Cargo.toml
[profile.release]
opt-level = "z"
lto = "thin"
codegen-units = 1
panic = "abort"
strip = "symbols"
overflow-checks = false
1.11.3 Go
bash
CGO_ENABLED=0 go build \
-ldflags="-s -w" \
-trimpath \
-o app .
1.11.4 Swift
SWIFT_OPTIMIZATION_LEVEL = -Osize
DEAD_CODE_STRIPPING = YES
STRIP_INSTALLED_PRODUCT = YES
SWIFT_COMPILATION_MODE = wholemodule
1.11.5 WebAssembly(Rust)
toml
[profile.release]
opt-level = "z"
lto = true
# .cargo/config.toml
[target.wasm32-unknown-unknown]
rustflags = ["-C", "link-arg=--export-dynamic"]
bash
wasm-opt -Oz --strip-debug input.wasm -o output.wasm
1.12 优化优先级建议
按照 投入产出比 排序,建议按此顺序逐步尝试:
| 优先级 | 措施 | 预期收益 | 风险 |
|---|---|---|---|
| ⭐⭐⭐⭐⭐ | -Os / -Oz 优化等级 |
10%~30% | 低 |
| ⭐⭐⭐⭐⭐ | --gc-sections + -ffunction-sections |
5%~40% | 低 |
| ⭐⭐⭐⭐⭐ | strip 调试信息 |
30%~60% | 无(保留 .dbg) |
| ⭐⭐⭐⭐ | LTO(ThinLTO 优先) | 5%~20% | 中(构建变慢) |
| ⭐⭐⭐⭐ | ICF(Identical Code Folding) | 3%~15% | 中 |
| ⭐⭐⭐⭐ | -fno-exceptions -fno-rtti |
5%~15% | 高(需代码改造) |
| ⭐⭐⭐ | 符号可见性控制 | 1%~5% | 低 |
| ⭐⭐⭐ | 替换重型依赖库 | 项目相关 | 高(需重测) |
| ⭐⭐⭐ | 资源压缩(图片/字体) | 项目相关 | 低 |
| ⭐⭐ | PGO 优化 | 5%~10% | 高(流程复杂) |
| ⭐⭐ | 模板实例化优化 | 项目相关 | 中 |
| ⭐ | 自定义 malloc(嵌入式) | 5%~20% | 高 |
| ⭐ | UPX 压缩 | 30%~50% | 冷启动变慢 |