rust交叉编译 simpileperf
注意:以下所有命令都假定你在 Windows 环境下使用 WSL/Git Bash 或 PowerShell,并针对 Android x86_64 架构 进行交叉编译和性能采集.
常见 simpleperf 编译命令与环境准备
1. NDK 路径确认 (Windows)
# Windows NDK LLVM 工具链路径
# 请将此路径设置为环境变量或在 config.toml 中使用
F:\Android\Sdk\ndk\29.0.14206865\toolchains\llvm\prebuilt\windows-x86_64\bin
需要替换为本机安装路径,这里我用的绝对路径
2. Android 平台信息检查 (Linux/Android Shell)
检查目标设备(手机或模拟器)的 CPU 架构和 API Level,以便正确选择交叉编译目标.
# [平台:Android Shell (adb shell)]
# 检查目标设备的 CPU 架构
getprop ro.product.cpu.abi
# 模拟器 output 示例:
# x86_64
# [平台:Android Shell (adb shell)]
# 检查 Android 版本
adb shell getprop ro.build.version.release
# output :
# 15
# 检查 Android API LEVEL (重要!)
adb shell getprop ro.build.version.sdk
# output 示例:
# 35
使用
x86_64-linux-android35-clang版本进行交叉编译,其中35是目标 API Level.
为 Rust 进行交叉编译环境配置
1. 添加 Rust 目标平台 (Shell/Bash)
# 所有的android平台
$ rustup target list | grep android
aarch64-linux-android
arm-linux-androideabi
armv7-linux-androideabi
i686-linux-android
thumbv7neon-linux-androideabi
x86_64-linux-android
# [平台:WSL / Git Bash (需要 PATH 中有 rustup)]
# 安装 Android 目标编译工具链
rustup target add x86_64-linux-android
# 临时镜像加速
# export RUSTUP_UPDATE_ROOT="https://mirrors.sjtug.sjtu.edu.cn/rust-static/rustup/"
# export RUSTUP_DIST_SERVER="https://mirrors.sjtug.sjtu.edu.cn/rust-static"
2. 检查当前编译链 (Shell/Bash)
# [平台:WSL / Git Bash]
# 检查当前 rust 安装的交叉编译目标
rustup target list --installed
# output
# x86_64-linux-android
# x86_64-pc-windows-msvc
配置 config.toml 文件 (Linker 指定)
该文件通常位于项目的
.cargo目录下
# =========================================================
# 对 x86_64 Android 设备的配置 (以 API 35 为例)
# =========================================================
[target.x86_64-linux-android]
# 链接器 (Linker) 的完整路径.必须指向 NDK LLVM bin 目录中的特定 clang/clang.cmd 文件.
# 我们指定针对 API 35 的版本.
# 注意: 路径中的双反斜杠 \\ 是 TOML 文件中表示 Windows 路径的常见写法.
linker = "F:\\Android\\Sdk\\ndk\\29.0.14206865\\toolchains\\llvm\\prebuilt\\windows-x86_64\\bin\\x86_64-linux-android35-clang.cmd"
# C 编译器 (CC) 的完整路径 (Rust 在编译 C/C++ 依赖时可能需要)
# cc = "F:\\Android\\Sdk\\ndk\\29.0.14206865\\toolchains\\llvm\\prebuilt\\windows-x86_64\\bin\\x86_64-linux-android35-clang.cmd"
# 告诉链接器目标平台和 API 级别
rustflags = [
"-C", "link-arg=-target",
"-C", "link-arg=x86_64-unknown-linux-android35"
]
# =========================================================
# 对 ARM 64 位 (aarch64) Android 设备的配置
# =========================================================
[target.aarch64-linux-android]
# 链接器路径修改为 aarch64 对应的文件
linker = "F:\\Android\\Sdk\\ndk\\29.0.14206865\\toolchains\\llvm\\prebuilt\\windows-x86_64\\bin\\aarch64-linux-android35-clang.cmd"
# 目标也改为 aarch64
rustflags = [
"-C", "link-arg=-target",
"-C", "link-arg=aarch64-unknown-linux-android35"
]
执行交叉编译
# [平台:WSL / Git Bash]
# 编译目标架构的二进制文件 (默认是 debug 模式 带上更多debug信息)
cargo build --target x86_64-linux-android
# 如果需要优化和更快执行速度
# cargo build --target x86_64-linux-android --release
simpleperf 采集与 FlameGraph 生成
1. 推送与采集 (Linux/Android Shell)
# [平台:Android Shell (adb shell)]
# 切换到临时目录
cd /data/local/tmp/rust_dir
# 采集 simpleperf 数据
# -g: 记录调用栈信息 (必须,用于 FlameGraph)
# -o: 输出文件
# --duration 5: 采集时间 5s
# ./cross_rust: 目标可执行文件
simpleperf record -g -o perf.data --duration 5 ./cross_rust
2. 拉取数据 (Shell/Bash)
# [平台:WSL / Git Bash (拉取到当前目录)]
adb pull /data/local/tmp/rust_dir/perf.data .
3. 生成 FlameGraph SVG (Python/Perl 脚本,Windows/Bash 环境)
注意:需要确保
FlameGraph目录(包含stackcollapse-perf.pl和flamegraph.pl)和simpleperfPython 脚本是可访问的.这里还需要自行安装perl
# [平台:WSL / Git Bash]
# 定义路径变量,便于阅读和修改
PERF_SCRIPT_PATH="F:/Android/Sdk/ndk/29.0.14206865/simpleperf/report_sample.py"
FLAMEGRAPH_DIR="D:/code/FlameGraph"
TARGET_DIR="D:/code/xxxxx_project/filament/rust_test/target/x86_64-linux-android/debug"
# 调用 simpleperf Python 脚本解析 perf.data,然后通过管道传递给 Perl 脚本生成 SVG
python "${PERF_SCRIPT_PATH}" -i perf.data --symfs "${TARGET_DIR}" \
| perl "${FLAMEGRAPH_DIR}/stackcollapse-perf.pl" \
| perl "${FLAMEGRAPH_DIR}/flamegraph.pl" > test_root.svg
抓到的火焰图
问题在于依然有大量的kernel函数(待解决)
附录代码
use std::cmp::Ordering;
use std::collections::BinaryHeap;
type NodeId = usize;
type Weight = u64;
// 定义一个非常大的值作为初始距离 (INF)
const INF: Weight = u64::MAX;
// --- 数据结构定义 ---
// 邻接表中的边
#[derive(Debug, Clone, Copy)]
struct Edge {
to: NodeId,
val: Weight,
next: isize, // next 的索引,-1 表示链表末尾
}
// 用于优先队列的结构体,用于 Prim 算法
// Rust 的 BinaryHeap 是 Max-Heap,所以需要实现自定义的比较来模拟 Min-Heap
#[derive(Debug, Clone, Eq, PartialEq)]
struct PriorityEdge {
u: NodeId, // 当前节点
v: NodeId, // 连接 u 的已在 MST 中的前驱节点 (用于记录 MST 边)
dis: Weight, // 边权值
}
impl Ord for PriorityEdge {
// 默认是最大堆,要模拟最小堆,让 dis 较小的元素“更大”
fn cmp(&self, other: &Self) -> Ordering {
// 比较 dis 的大小,但顺序相反
other.dis.cmp(&self.dis)
}
}
impl PartialOrd for PriorityEdge {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
// 记录 MST 边的结构体
#[derive(Debug)]
struct MstEdge {
u: NodeId,
v: NodeId,
val: Weight,
}
// --- 图结构和算法实现 ---
struct Graph {
// 最大节点数,与 C++ 中的 N = 2e6+9 对应
// 实际使用时,Rust 更推荐使用 Vec<T> 或 HashMap 来动态管理内存
// 这里为了保持结构相似性,我们使用一个大数组,并只使用 n 个元素
max_size: usize,
// 邻接表
head: Vec<isize>,
e: Vec<Edge>,
idx: usize,
// Prim 算法状态
dist: Vec<Weight>,
vis: Vec<bool>,
// 图的参数
n: NodeId,
// 结果
sum: Weight,
cnt: NodeId,
tt: Vec<MstEdge>, // MST 边的集合
}
impl Graph {
fn new(max_size: usize) -> Self {
Graph {
max_size,
head: vec![-1; max_size],
e: Vec::with_capacity(max_size * 2),
idx: 0,
dist: vec![INF; max_size],
vis: vec![false; max_size],
n: 0,
sum: 0,
cnt: 0,
tt: Vec::new(),
}
}
fn add_edge(&mut self, u: NodeId, v: NodeId, val: Weight) {
// Rust 的数组是从 0 开始的,这里假设节点 ID 从 1 开始,所以使用 u-1 和 v-1
let u_idx = u - 1;
let new_edge = Edge {
to: v,
val,
next: self.head[u_idx],
};
self.e.push(new_edge);
self.head[u_idx] = self.idx as isize;
self.idx += 1;
}
// 对应 C++ 的 bd() 函数,但内置了数据
fn build_with_sample_data(&mut self) {
// 示例图:5 个节点,7 条边
let edges: Vec<(NodeId, NodeId, Weight)> = vec![
(1, 2, 2),
(1, 3, 4),
(2, 3, 1),
(2, 4, 7),
(3, 5, 5),
(4, 5, 3),
(4, 6, 8), // 增加一个节点和边,确保图不连通 (N=5, M=7)
];
// 实际节点数 N
self.n = 5;
// 重置 dist, vis, head 确保只使用 self.n 大小
self.dist.fill(INF);
self.vis.fill(false);
self.head.fill(-1);
for &(u, v, val) in &edges {
if u <= self.n && v <= self.n {
// 添加双向边
self.add_edge(u, v, val);
self.add_edge(v, u, val);
}
}
println!("图已构建.节点数: {} (仅使用前 {} 个节点).", self.n, self.n);
}
// 对应 C++ 的 Prim() 函数
fn prim(&mut self) {
let src: NodeId = 1; // 从节点 1 开始
let src_idx = src - 1;
let mut pq: BinaryHeap<PriorityEdge> = BinaryHeap::new();
self.dist[src_idx] = 0;
// 优先级队列存储 (u, v, dis),v 是 u 的前驱节点,初始时 v=u
pq.push(PriorityEdge {
u: src,
v: src,
dis: 0,
});
while let Some(current_edge) = pq.pop() {
let u = current_edge.u;
let dis = current_edge.dis;
let prev_v = current_edge.v; // 已在 MST 中的前驱节点
let u_idx = u - 1;
if self.vis[u_idx] {
continue;
}
self.vis[u_idx] = true;
self.cnt += 1;
self.sum += dis;
// 记录 MST 边 (跳过起始点到自身的边)
if dis != 0 {
self.tt.push(MstEdge {
u,
v: prev_v,
val: dis,
});
}
// 遍历 u 的邻居
let mut edge_index = self.head[u_idx];
while edge_index != -1 {
// 确保索引有效
let i = edge_index as usize;
let neighbor = self.e[i].to;
let val = self.e[i].val;
let neighbor_idx = neighbor - 1;
// 松弛操作
if !self.vis[neighbor_idx] && val < self.dist[neighbor_idx] {
self.dist[neighbor_idx] = val;
// 推入优先级队列:节点 u 连接到新的节点 neighbor,权值为 val
pq.push(PriorityEdge {
u: neighbor,
v: u,
dis: val,
});
}
edge_index = self.e[i].next;
}
}
}
// 对应 C++ 的 print() 函数
fn print_mst_edges(&self) {
println!("\n--- 最小生成树 (MST) 的边 ---");
for edge in &self.tt {
println!("{} -- {} (Weight: {})", edge.u, edge.v, edge.val);
}
}
}
// --- Main 函数 ---
fn main() {
// 保持与 C++ 的 main 函数一致
// 注意 Rust 默认的 IO 速度已经很快,不需要额外的同步操作.
// 假设最大的节点 ID 不会超过 1000,简化内存分配
const MAX_NODES: usize = 1000;
let mut graph = Graph::new(MAX_NODES);
// 1. 构建图 (bd())
graph.build_with_sample_data();
// 2. 运行 Prim 算法
graph.prim();
// 3. 输出结果
// Prim 算法只会找到与起点连通的组件的 MST
// 如果 cnt 等于 n,则表示整个图是连通的,找到了完整的 MST
if graph.cnt == graph.n {
println!("\n 成功找到最小生成树 !");
println!("总权值: {}", graph.sum);
graph.print_mst_edges();
} else {
println!("\nNO MST");
println!("图不连通.仅找到 {} 个节点的 MST.", graph.cnt);
}
}
输出
图已构建.节点数: 5 (仅使用前 5 个节点).
成功找到最小生成树 !
总权值: 11
--- 最小生成树 (MST) 的边 ---
2 -- 1 (Weight: 2)
3 -- 2 (Weight: 1)
5 -- 3 (Weight: 5)
4 -- 5 (Weight: 3)

浙公网安备 33010602011771号