Skip to content

性能优化与调试

概述

性能优化和调试是嵌入式系统开发的关键环节。LeBot 机器人需要在有限的硬件资源下运行,因此高效的性能优化和有效的调试方法至关重要。本章将介绍 ARM 平台上的性能优化技巧和调试工具。

编译优化

编译优化级别

Rust 提供了多个编译优化级别,适用于不同的场景。

优化级别详解

toml
# 开发版本(快速编译,便于调试)
[profile.dev]
opt-level = 0           # 无优化
debug = true            # 保留调试信息
debug-assertions = true # 启用调试断言
overflow-checks = true  # 检查整数溢出
lto = false            # 无链接时优化
codegen-units = 256    # 高度并行化编译

# 发布版本(优化性能和大小)
[profile.release]
opt-level = 3           # 最高级别优化
debug = false           # 不保留调试信息
debug-assertions = false # 禁用调试断言
overflow-checks = false # 不检查整数溢出
lto = true             # 启用链接时优化
codegen-units = 1      # 单一代码生成单元

# 自定义优化配置
[profile.release-optimized]
inherits = "release"
opt-level = 3
lto = "fat"            # 最激进的 LTO
strip = true           # 移除符号表

针对 ARM 的特定优化

toml
[profile.release]
# ARMv7 特定优化
rustflags = [
    # 使用 NEON SIMD 指令集
    "-C", "target-feature=+neon",
    
    # 针对特定 CPU 优化
    "-C", "tune=cortex-a7",
    
    # 启用内联优化
    "-C", "inline-threshold=1000",
    
    # 使用 ARM 硬件 FPU
    "-C", "float-abi=hard",
]

编译时间 vs 运行时性能的权衡

rust
// 使用条件编译处理不同场景
#[cfg(debug_assertions)]
pub fn calculate_position() {
    // 开发版本:可能较慢但易于调试
    verify_invariants();
    let result = expensive_calculation();
    verify_result(&result);
}

#[cfg(not(debug_assertions))]
pub fn calculate_position() {
    // 发布版本:优化过的快速计算
    fast_calculation()
}

性能分析

使用 perf 工具进行性能分析

bash
# 安装 perf
sudo apt-get install linux-tools-generic

# 编译带调试信息的版本
cargo build --target armv7-unknown-linux-gnueabihf

# 记录性能数据
perf record -g ./target/armv7-unknown-linux-gnueabihf/debug/lebot_arm

# 生成性能报告
perf report

# 查看火焰图
perf script > perf.data.txt
# 使用 flamegraph 工具可视化

使用 flamegraph 分析

bash
# 安装 flamegraph
cargo install flamegraph

# 生成火焰图
cargo flamegraph --bin lebot_arm -- --release

# 输出为 SVG 图像
# flame_graph.svg

使用 Valgrind 检测内存问题

bash
# 安装 Valgrind
sudo apt-get install valgrind

# 检测内存泄漏
valgrind --leak-check=full \
         --show-leak-kinds=all \
         ./target/armv7-unknown-linux-gnueabihf/release/lebot_arm

# 检测缓冲区溢出
valgrind --tool=memcheck ./lebot_arm

# 检测数据竞争(多线程)
valgrind --tool=helgrind ./lebot_arm

集成性能测试

rust
// benches/performance.rs
#[cfg(test)]
mod benchmarks {
    use std::time::Instant;
    
    #[test]
    fn bench_motion_calculation() {
        let start = Instant::now();
        let iterations = 100_000;
        
        for _ in 0..iterations {
            calculate_motion();
        }
        
        let elapsed = start.elapsed();
        let avg_time = elapsed.as_micros() as f64 / iterations as f64;
        
        println!("平均时间: {:.2} μs", avg_time);
        assert!(avg_time < 100.0, "性能下降");
    }
    
    fn calculate_motion() {
        // 模拟动作计算
    }
}

内存优化

栈内存优化

rust
// 不推荐:分配大量栈内存
fn process_large_data_bad() {
    let mut buffer = [0u8; 1024 * 1024];  // 1MB 栈分配
    // 栈溢出风险
}

// 推荐:使用堆内存
fn process_large_data_good() {
    let mut buffer = vec![0u8; 1024 * 1024];  // 堆分配
    // 安全、灵活
}

// 推荐:使用栈分配器(custom allocator)
#[global_allocator]
static GLOBAL: std::alloc::System = std::alloc::System;

减少内存分配

rust
// 重复分配 - 性能差
fn bad_approach(data: Vec<u32>) -> Vec<u32> {
    let mut result = Vec::new();
    
    for item in data {
        let processed = process_item(item);
        result.push(processed);  // 每次都可能重新分配
    }
    
    result
}

// 预先分配 - 性能好
fn good_approach(data: Vec<u32>) -> Vec<u32> {
    let mut result = Vec::with_capacity(data.len());
    
    for item in data {
        let processed = process_item(item);
        result.push(processed);  // 使用预先分配的空间
    }
    
    result
}

// 就地修改 - 最优
fn best_approach(mut data: Vec<u32>) -> Vec<u32> {
    data.iter_mut().for_each(|item| {
        *item = process_item(*item);  // 无额外分配
    });
    data
}

内存池(Object Pool)模式

rust
use std::sync::Mutex;

pub struct MotionStatePool {
    available: Mutex<Vec<MotionState>>,
    created: std::sync::atomic::AtomicUsize,
}

pub struct MotionState {
    position: [f32; 3],
    velocity: [f32; 3],
    acceleration: [f32; 3],
}

impl MotionStatePool {
    pub fn new(capacity: usize) -> Self {
        let mut available = Vec::with_capacity(capacity);
        for _ in 0..capacity {
            available.push(MotionState::default());
        }
        
        MotionStatePool {
            available: Mutex::new(available),
            created: std::sync::atomic::AtomicUsize::new(capacity),
        }
    }
    
    pub fn acquire(&self) -> Option<MotionState> {
        if let Ok(mut available) = self.available.lock() {
            available.pop()
        } else {
            None
        }
    }
    
    pub fn release(&self, state: MotionState) {
        if let Ok(mut available) = self.available.lock() {
            if available.len() < 100 {  // 保持合理大小
                available.push(state);
            }
        }
    }
}

impl Default for MotionState {
    fn default() -> Self {
        MotionState {
            position: [0.0; 3],
            velocity: [0.0; 3],
            acceleration: [0.0; 3],
        }
    }
}

CPU 优化

向量化计算

rust
// 标量实现 - 较慢
fn calculate_distance_scalar(a: &[f32], b: &[f32]) -> f32 {
    let mut sum = 0.0;
    for i in 0..a.len() {
        let diff = a[i] - b[i];
        sum += diff * diff;
    }
    sum.sqrt()
}

// SIMD 实现 - 更快
#[cfg(target_arch = "arm")]
use std::arch::arm::*;

#[cfg(target_arch = "arm")]
fn calculate_distance_simd(a: &[f32], b: &[f32]) -> f32 {
    unsafe {
        let mut sum_v = vdupq_n_f32(0.0);
        
        let chunks = a.len() / 4;
        for i in 0..chunks {
            let a_v = vld1q_f32(&a[i * 4]);
            let b_v = vld1q_f32(&b[i * 4]);
            let diff_v = vsubq_f32(a_v, b_v);
            let sq_v = vmulq_f32(diff_v, diff_v);
            sum_v = vaddq_f32(sum_v, sq_v);
        }
        
        // 求和向量元素
        let mut sum: [f32; 4] = [0.0; 4];
        vst1q_f32(sum.as_mut_ptr(), sum_v);
        
        (sum[0] + sum[1] + sum[2] + sum[3]).sqrt()
    }
}

缓存优化

rust
// 缓存不友好 - 随机访问
fn cache_unfriendly(data: &[u32; 10000]) {
    for i in (0..10000).step_by(1000) {  // 巨大步幅
        process(data[i]);
    }
}

// 缓存友好 - 顺序访问
fn cache_friendly(data: &[u32; 10000]) {
    for i in 0..10000 {
        process(data[i]);  // 顺序访问
    }
}

// 使用数据对齐
#[repr(align(64))]  // L3 缓存行大小
struct CacheAlignedData {
    data: [f32; 16],
}

多线程优化

减少锁竞争

rust
use std::sync::{Arc, Mutex, RwLock};
use std::sync::atomic::{AtomicU32, Ordering};

// 问题:过度使用 Mutex
struct BadRobotState {
    state: Mutex<RobotState>,
}

impl BadRobotState {
    fn get_position(&self) -> [f32; 3] {
        self.state.lock().unwrap().position
    }
    
    fn set_position(&self, pos: [f32; 3]) {
        self.state.lock().unwrap().position = pos;
    }
}

// 改进:使用无锁原子操作
struct GoodRobotState {
    x: AtomicU32,
    y: AtomicU32,
    z: AtomicU32,
}

impl GoodRobotState {
    fn get_x(&self) -> f32 {
        f32::from_bits(self.x.load(Ordering::Relaxed))
    }
    
    fn set_x(&self, val: f32) {
        self.x.store(val.to_bits(), Ordering::Relaxed);
    }
}

// 最优:RwLock 用于读多写少
struct OptimalRobotState {
    state: RwLock<RobotState>,
}

impl OptimalRobotState {
    fn get_position(&self) -> [f32; 3] {
        self.state.read().unwrap().position  // 不阻塞其他读者
    }
}

任务调度优化

rust
use std::thread;
use std::sync::mpsc;

pub struct TaskScheduler {
    workers: Vec<thread::JoinHandle<()>>,
    sender: mpsc::Sender<Task>,
}

pub enum Task {
    MotionControl,
    SensorReading,
    DataProcessing,
}

impl TaskScheduler {
    pub fn new(num_workers: usize) -> Self {
        let (sender, receiver) = mpsc::channel();
        let receiver = Arc::new(Mutex::new(receiver));
        
        let mut workers = Vec::new();
        
        for _ in 0..num_workers {
            let receiver = Arc::clone(&receiver);
            let worker = thread::spawn(move || {
                loop {
                    let task = receiver.lock().unwrap().recv();
                    match task {
                        Ok(Task::MotionControl) => handle_motion(),
                        Ok(Task::SensorReading) => handle_sensors(),
                        Ok(Task::DataProcessing) => process_data(),
                        Err(_) => break,
                    }
                }
            });
            workers.push(worker);
        }
        
        TaskScheduler { workers, sender }
    }
}

调试工具与技巧

使用 GDB 远程调试

bash
# 在 ARM 目标设备上运行 gdbserver
ssh pi@device
gdbserver localhost:3333 /path/to/lebot_arm

# 在开发机上连接
arm-linux-gnueabihf-gdb target/armv7-unknown-linux-gnueabihf/debug/lebot_arm

# GDB 命令
(gdb) target remote device:3333
(gdb) file target/armv7-unknown-linux-gnueabihf/debug/lebot_arm
(gdb) break main
(gdb) continue
(gdb) step
(gdb) next
(gdb) print variable_name
(gdb) backtrace

使用 LLDB 调试

bash
# 安装 LLDB
sudo apt-get install lldb

# 编译为调试版本
cargo build --target armv7-unknown-linux-gnueabihf

# 启动 LLDB
lldb target/armv7-unknown-linux-gnueabihf/debug/lebot_arm

# LLDB 命令
(lldb) break set --name main
(lldb) run
(lldb) frame variable
(lldb) thread backtrace

日志系统

rust
use log::{info, debug, error, warn};
use env_logger;

fn main() {
    // 初始化日志系统
    env_logger::init();
    
    info!("LeBot 启动");
    debug!("调试信息");
    error!("错误信息");
    warn!("警告信息");
}

// 使用环境变量控制日志级别
// RUST_LOG=debug cargo run
// RUST_LOG=lebot=trace cargo run

断言和验证

rust
// 开发时的断言
pub fn set_motor_speed(motor_id: usize, speed: f32) {
    debug_assert!(motor_id < 8, "电机 ID 超出范围");
    debug_assert!((speed >= -1.0) && (speed <= 1.0), "速度超出范围");
    
    // 实际操作
    unsafe {
        set_motor_hardware(motor_id, speed);
    }
}

// 运行时验证
pub fn calculate_inverse_kinematics(positions: &[f32]) -> Result<Vec<f32>, String> {
    if positions.len() != 3 {
        return Err("需要 3 维位置数据".to_string());
    }
    
    if positions.iter().any(|&p| !p.is_finite()) {
        return Err("包含无效的浮点数".to_string());
    }
    
    // 继续计算
    Ok(Vec::new())
}

实战案例:优化 LeBot 步行循环

优化前的代码

rust
// 性能较差的版本
fn robot_control_loop() {
    loop {
        let mut state = get_robot_state();  // 每次都分配
        
        // 从多个传感器读取
        state.imu_data = read_imu_sensor();
        state.encoders = read_encoders();
        state.contacts = read_contact_sensors();
        
        // 计算运动
        let angles = calculate_joint_angles(&state);
        
        // 应用控制
        for (i, angle) in angles.iter().enumerate() {
            set_motor_angle(i, *angle);
        }
        
        std::thread::sleep(std::time::Duration::from_millis(10));
    }
}

优化后的代码

rust
// 性能优化版本
struct RobotControlLoop {
    state: RobotState,
    angles: Vec<f32>,
    control_period: std::time::Duration,
}

impl RobotControlLoop {
    fn new() -> Self {
        RobotControlLoop {
            state: RobotState::default(),
            angles: vec![0.0; 8],  // 预分配
            control_period: std::time::Duration::from_millis(10),
        }
    }
    
    #[inline]
    fn run(&mut self) {
        loop {
            let start = std::time::Instant::now();
            
            // 就地更新状态
            update_state_inplace(&mut self.state);
            
            // 计算并存储角度
            calculate_joint_angles_inplace(&self.state, &mut self.angles);
            
            // 快速应用控制
            apply_motor_commands(&self.angles);
            
            // 精确睡眠
            let elapsed = start.elapsed();
            if elapsed < self.control_period {
                std::thread::sleep(self.control_period - elapsed);
            }
        }
    }
}

#[inline]
fn update_state_inplace(state: &mut RobotState) {
    // 直接更新,无分配
    state.imu_data = read_imu_sensor();
    state.encoders = read_encoders();
    state.contacts = read_contact_sensors();
}

#[inline]
fn calculate_joint_angles_inplace(state: &RobotState, angles: &mut Vec<f32>) {
    // 在预分配的向量中计算
    for i in 0..8 {
        angles[i] = compute_angle_for_joint(i, state);
    }
}

#[inline]
fn apply_motor_commands(angles: &[f32]) {
    // 最小化函数调用
    unsafe {
        for (i, &angle) in angles.iter().enumerate() {
            set_motor_angle_direct(i, angle);
        }
    }
}

性能监控

运行时性能指标收集

rust
pub struct PerformanceMetrics {
    loop_times: std::collections::VecDeque<std::time::Duration>,
    max_loop_time: std::time::Duration,
}

impl PerformanceMetrics {
    pub fn new() -> Self {
        PerformanceMetrics {
            loop_times: std::collections::VecDeque::with_capacity(100),
            max_loop_time: std::time::Duration::ZERO,
        }
    }
    
    pub fn record_loop_time(&mut self, duration: std::time::Duration) {
        if self.loop_times.len() == 100 {
            self.loop_times.pop_front();
        }
        
        self.loop_times.push_back(duration);
        self.max_loop_time = self.max_loop_time.max(duration);
    }
    
    pub fn get_average_loop_time(&self) -> std::time::Duration {
        if self.loop_times.is_empty() {
            return std::time::Duration::ZERO;
        }
        
        let sum: std::time::Duration = self.loop_times.iter().sum();
        sum / self.loop_times.len() as u32
    }
    
    pub fn print_stats(&self) {
        println!("控制循环统计:");
        println!("  平均时间: {:?}", self.get_average_loop_time());
        println!("  最大时间: {:?}", self.max_loop_time);
    }
}

总结

性能优化和调试是一个持续的过程。关键点包括:

  1. 测量优先 - 使用性能分析工具
  2. 优化瓶颈 - 专注于最耗时的操作
  3. 避免过度优化 - 保持代码可读性
  4. 充分测试 - 确保优化不会引入 bug
  5. 定期监控 - 持续追踪性能指标

在 LeBot 项目中,实时性能至关重要,应该始终保持对控制循环执行时间的监控。

推荐资源

由 LeBot 开发团队编写