4. 性能剖析工具:CPU占用率分析、中断延迟测量、任务切换时间

性能剖析,说白了就是给系统做体检。CPU占用率、中断延迟、任务切换时间,这三项是嵌入式系统性能的核心指标。我做了这么多年嵌入式,见过太多系统跑着跑着就卡死、响应慢、丢数据,最后查下来,基本都是这三个指标出了问题。

这一章,我们就来聊聊怎么测量、分析、优化这三个关键指标。我会结合我实际项目中的经验,给你一些实用的方法和工具。

4.1 CPU占用率分析

CPU占用率,就是CPU忙活的时间占总时间的比例。太高了,系统响应慢;太低了,资源浪费。理想情况是保持在60%-80%之间。

4.1.1 测量方法

测量CPU占用率,常用的方法有两种:

  • 硬件定时器法:用一个高精度定时器,统计空闲任务运行的时间。空闲任务跑得越多,CPU占用率越低。
  • 软件插桩法:在任务切换点插入代码,记录每个任务的运行时间。这种方法更精细,但会增加系统开销。

我个人习惯用硬件定时器法,因为它对系统影响小,适合长期监控。下面是一个简单的实现示例:

// 伪代码:使用硬件定时器测量CPU占用率
static uint32_t idle_ticks = 0;
static uint32_t total_ticks = 0;

void idle_task(void) {
    while(1) {
        // 记录空闲开始时间
        uint32_t start = get_timer_ticks();
        // 执行空闲操作(如WFI)
        __WFI();
        // 记录空闲结束时间
        idle_ticks += (get_timer_ticks() - start);
        total_ticks = get_timer_ticks();
    }
}

float get_cpu_usage(void) {
    // CPU占用率 = (总时间 - 空闲时间) / 总时间 * 100%
    return (1.0f - (float)idle_ticks / total_ticks) * 100.0f;
}
小技巧:测量周期建议设为1秒。太短了数据抖动大,太长了反应迟钝。我一般用1秒的滑动窗口,既平滑又及时。

4.1.2 分析思路

拿到CPU占用率数据后,怎么分析?我一般按这个步骤来:

  1. 看整体:先看平均占用率。如果超过90%,系统大概率有问题。
  2. 看峰值:再看瞬时峰值。如果峰值超过95%,说明有突发任务占用了大量CPU。
  3. 看分布:最后看各个任务的占用率。哪个任务吃CPU最多,就重点优化哪个。
注意:我曾经在一个项目中,发现CPU占用率只有50%,但系统响应却很慢。查了半天,原来是中断太频繁,CPU大部分时间都在处理中断,而不是执行任务。所以,CPU占用率低不代表系统性能好,还要结合中断延迟一起看。

4.2 中断延迟测量

中断延迟,就是从硬件触发中断,到CPU开始执行中断服务函数的时间。这个时间越短,系统实时性越好。我见过一个项目,中断延迟从10us飙到100us,结果数据采集直接丢包。

4.2.1 测量方法

测量中断延迟,最直接的方法是用示波器。但有时候条件不允许,我们也可以用软件来测。方法如下:

  • GPIO翻转法:在中断触发时,翻转一个GPIO;在中断服务函数开始处,再翻转一次。用示波器测量两次翻转的时间差,就是中断延迟。
  • 定时器捕获法:用定时器的捕获功能,记录中断触发时刻和CPU响应时刻。这种方法精度高,但需要硬件支持。

下面是一个用GPIO翻转法测量的示例:

// 伪代码:GPIO翻转法测量中断延迟
void interrupt_handler(void) {
    // 记录中断触发时刻(由硬件自动设置)
    // 这里假设GPIO在中断触发时已经翻转
    
    // 在中断服务函数开始处翻转GPIO
    GPIO_TogglePin(GPIO_PORT, GPIO_PIN);
    
    // 处理中断
    // ...
    
    // 清除中断标志
    clear_interrupt_flag();
}
关键点:测量中断延迟时,要关闭所有其他中断,避免干扰。我一般会测100次,取最大值和平均值。最大值决定了系统的实时性上限。

4.2.2 影响因素

中断延迟受哪些因素影响?我总结了一下:

因素 影响 优化方法
中断优先级 高优先级中断会抢占低优先级 合理分配优先级,关键中断设最高
中断嵌套 嵌套越深,延迟越大 尽量减少嵌套,使用中断分组
临界区长度 临界区越长,中断响应越慢 缩短临界区,使用无锁数据结构
CPU频率 频率越高,延迟越低 在功耗允许下提高频率
避坑指南:我曾经在一个项目中,把中断优先级设得乱七八糟,结果高优先级中断一直抢占,低优先级中断根本得不到响应。后来我用了中断分组,把关键中断放在一组,非关键中断放在另一组,问题才解决。

4.3 任务切换时间

任务切换时间,就是从一个任务切换到另一个任务所需的时间。这个时间越短,系统响应越快。但任务切换是有代价的,每次切换都要保存和恢复上下文。

4.3.1 测量方法

测量任务切换时间,我一般用两种方法:

  • GPIO翻转法:在每个任务开始和结束时翻转GPIO,用示波器测量两次翻转的时间差。
  • 系统Tick法:利用操作系统的系统Tick,记录任务切换的时刻。这种方法精度较低,但方便。

下面是一个用系统Tick法测量的示例:

// 伪代码:系统Tick法测量任务切换时间
static uint32_t last_switch_tick = 0;

void task_switch_hook(void) {
    uint32_t current_tick = get_system_tick();
    uint32_t switch_time = current_tick - last_switch_tick;
    last_switch_tick = current_tick;
    
    // 记录切换时间
    if (switch_time > max_switch_time) {
        max_switch_time = switch_time;
    }
}
小技巧:任务切换时间一般只有几十微秒,用系统Tick法可能测不准。我建议用GPIO翻转法,精度高,而且直观。

4.3.2 优化方法

任务切换时间长了怎么办?我一般从这几个方面入手:

  1. 减少任务数量:任务越多,切换越频繁。能合并的任务就合并。
  2. 降低优先级:优先级高的任务切换频繁,适当降低优先级可以减少切换。
  3. 使用信号量:用信号量代替轮询,可以减少不必要的任务切换。
  4. 优化上下文保存:有些CPU支持硬件上下文保存,可以大大减少切换时间。
经验之谈:我做过一个项目,任务切换时间占了CPU的30%。后来我把几个小任务合并成一个,切换时间直接降到了5%。所以,任务切换时间不是小事,一定要重视。

4.4 知识体系图

下面这张图,展示了性能剖析的核心逻辑和工具关系:

性能剖析核心指标与工具 性能剖析 CPU占用率分析 中断延迟测量 任务切换时间 硬件定时器法 软件插桩法 GPIO翻转法 定时器捕获法 系统Tick法 核心思路 1. CPU占用率:用空闲任务时间反推,关注峰值和分布 2. 中断延迟:用GPIO翻转或定时器捕获,关注最大值 3. 任务切换:用GPIO翻转或系统Tick,关注平均和峰值

4.5 实战案例

最后,分享一个我实际遇到的案例。一个数据采集系统,CPU占用率只有40%,但数据经常丢包。我一开始以为是网络问题,查了半天没找到原因。

后来我用GPIO翻转法测了中断延迟,发现最大值达到了200us。而数据采集的中断要求响应时间不超过50us。问题找到了:中断延迟太大,导致数据丢失。

进一步分析,发现中断延迟大的原因是临界区太长。有个任务在临界区里做了大量计算,导致中断被阻塞。我把计算移出临界区,中断延迟降到了20us,数据丢包问题彻底解决。

经验总结:性能剖析不是看一个指标就够了,要综合看CPU占用率、中断延迟、任务切换时间。这三个指标互相影响,只有全面分析,才能找到真正的瓶颈。