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.plflamegraph.pl)和 simpleperf Python 脚本是可访问的.这里还需要自行安装 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

抓到的火焰图

user-mode
问题在于依然有大量的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)
posted @ 2025-12-29 18:37  phrink  阅读(7)  评论(0)    收藏  举报