前面四篇的 ARM 用例,都是在 x86_64 机器上交叉编译成 aarch64、再用 QEMU 跑出来的。工欲善其事,必先利其器——这一篇把这条「工具链」拆开讲清楚:QEMU 交叉编译环境怎么搭、perf 怎么用、valgrind 怎么用,并给出可以直接照抄的命令。
一、QEMU 交叉编译环境搭建
1. 为什么用 QEMU
开发机通常是 x86,但目标跑在 ARM(手机 / 单板 / 服务器)。qemu-aarch64 是用户态模拟器,能直接在 x86 上运行 aarch64 二进制,不用真机就能验证 ARM 代码、跑分对比。这是本项目「ARM 一列实测数据」的来源。
2. 装交叉编译器(免 root)
在没 sudo 的环境里,可以用 apt-get download 把包下下来、再用 dpkg -x 解压到本地目录:
# 交叉编译器 + aarch64 的 glibc/头文件
apt-get download gcc-aarch64-linux-gnu libc6-dev-arm64-cross linux-libc-dev-arm64-cross
mkdir -p toolchain && for d in *.deb; do dpkg -x "$d" toolchain/; done
3. 装 qemu-aarch64-static
apt-get download qemu-user-static
dpkg -x qemu-user-static_*.deb qemu/
cp qemu/usr/bin/qemu-aarch64-static toolchain/qemu-aarch64-static
⚠️ 踩坑:GitHub 上 multiarch 仓库的 qemu-aarch64-static 老版本(v7.2.0)在本机一启动就段错误,改用 Ubuntu 官方包里的 qemu-aarch64-static 8.2.2 才正常。所以优先用发行版自带的包。
4. 编译 + 运行
# 静态链接,省去在 QEMU 里配动态链接器和 libc 路径的麻烦
aarch64-linux-gnu-gcc -static -O2 test.c -o test.aarch64
./qemu-aarch64-static test.aarch64
5. 一键脚本
这套流程我固化到了 arm-perf 项目里:
scripts/fetch-toolchain.sh # 自动下载交叉编译器 + qemu(幂等)
scripts/aarch64-cc.sh # 交叉编译 wrapper(自动带 sysroot/binutils)
make aarch64 # 交叉编译全部用例
make run-arm # 用 qemu-aarch64 批量跑分
二、perf 性能分析
perf 是 Linux 内核自带的性能分析工具,基于 PMU 硬件计数器,能看整体指标、也能定位热点函数。
1. perf stat:整体计数
perf stat -e cycles,instructions,cache-misses,branch-misses ./a.out
重点看 IPC = instructions / cycles(越高越好)和 cache-misses(访存瓶颈)。
2. perf record + report:找热点函数
perf record -g ./a.out # -g 记录调用栈
perf report # 交互式查看热点函数占比
3. perf top:实时观察
perf top # 像 top 一样实时显示热点
4. 火焰图(可选,最直观)
perf record -g ./a.out
perf script | stackcollapse-perf.pl | flamegraph.pl > flame.svg
5. 权限问题(高频坑)
如果报 Access to performance monitoring ... is limited,是 perf_event_paranoid 在限制:
cat /proc/sys/kernel/perf_event_paranoid # 4 = 完全禁用
sudo sysctl kernel.perf_event_paranoid=1 # 需要 root 放开
在容器 / 无 sudo 环境(比如本项目)perf 会被锁死,此时退回到进程内高精度计时(clock_gettime(CLOCK_MONOTONIC))兜底——这也是 arm-perf 用例的计时方式。
三、valgrind 分析
valgrind 是动态二进制分析工具,无需重新编译,但需要先安装:sudo apt install valgrind。
1. memcheck:内存错误 / 泄漏
valgrind --leak-check=full ./a.out
2. callgrind:函数调用耗时
valgrind --tool=callgrind ./a.out
callgrind_annotate callgrind.out.<pid>
3. cachegrind:缓存命中率
valgrind --tool=cachegrind ./a.out
cg_annotate cachegrind.out.<pid>
看 D1mr / DLmr(一级/末级缓存读未命中率),判断访存是否友好。
4. massif:堆内存占用
valgrind --tool=massif ./a.out
ms_print massif.out.<pid>
四、小结:工具怎么选
| 目标 | 工具 |
|---|---|
| 整体指标(cycles / IPC / cache-miss) | perf stat |
| 找热点函数 | perf record + report |
| 缓存行为 / 访存友好度 | cachegrind / perf stat -e cache-misses |
| 内存泄漏 / 越界 | valgrind memcheck |
| 函数调用耗时 | callgrind |
| ARM 代码在 x86 上验证 | qemu-aarch64 + 交叉编译 |
一句话:先 perf stat 看整体有没有瓶颈,再 perf record 定位热点函数,访存问题用 cachegrind,内存问题用 memcheck,ARM 验证用 QEMU。
本系列回到《传统ARM优化的思考》看总述,或翻「性能优化」分类下的其他文章。
