FreeRTOS学习(三十八)——性能优化

上一节学习了FreeRTOS常见错误与工程规范,知道了任务不阻塞、优先级设置混乱、任务栈不合理等问题都会影响系统稳定性。

当程序已经能够稳定运行以后,还可以进一步考虑:

复制代码
CPU有没有被浪费?

任务栈是不是给得太大?

Heap是不是占用太多?

任务是不是创建得太多?

任务之间的通信方式是否合适?

这些就属于FreeRTOS的性能优化

本节学习目标:掌握几个最常用的FreeRTOS优化方向,能够从CPU占用、任务栈、Heap、任务数量和任务通信几个方面检查自己的工程。


一、第一步不是优化,而是先测量

很多人优化程序时会直接:

复制代码
减小任务栈

提高任务优先级

减少Delay

这种方法并不可靠。

正确思路应该是:

复制代码
先测量
  ↓
找到问题
  ↓
再优化

例如我们前面已经学习过:

复制代码
uxTaskGetStackHighWaterMark();

可以检查任务栈。

复制代码
xPortGetFreeHeapSize();

可以检查剩余Heap。

复制代码
vTaskGetRunTimeStats();

可以观察各任务CPU运行时间。

只有知道:

复制代码
资源到底消耗在哪里

才能进行有针对性的优化。


二、避免任务无意义占用CPU

假设有一个按键任务:

复制代码
void KeyTask(void *pvParameters)
{
    while(1)
    {
        KeyScan();
    }
}

如果KeyScan()执行非常快,那么CPU可能会不断执行:

复制代码
扫描
 ↓
扫描
 ↓
扫描
 ↓
扫描

但是按键根本没有必要每时每刻扫描。

可以改成:

复制代码
void KeyTask(void *pvParameters)
{
    while(1)
    {
        KeyScan();

        vTaskDelay(
            pdMS_TO_TICKS(20)
        );
    }
}

这样任务每:

复制代码
20ms

检查一次按键。

剩余时间:

复制代码
任务进入Blocked

CPU就可以去运行其他任务。

所以优化FreeRTOS程序非常重要的一点就是:

没有事情做的任务,不要让它一直处于Ready状态。

三、能阻塞等待,就不要循环查询

例如通信任务等待数据:

复制代码
while(1)
{
    if(DataReady == 1)
    {
        SendData();
        DataReady = 0;
    }
}

这个任务会一直查询:

复制代码
DataReady有没有变成1?

大多数CPU时间其实都浪费在:

复制代码
检查一个没有变化的变量。

如果使用队列:

复制代码
xQueueReceive(
    DataQueue,
    &Data,
    portMAX_DELAY
);

没有数据时:

复制代码
任务直接阻塞。

有数据以后:

复制代码
FreeRTOS自动唤醒任务。

类似的还有:

复制代码
Semaphore

Task Notification

Event Group

所以在FreeRTOS中应该逐渐形成:

复制代码
事件没有发生
    ↓
任务阻塞

事件发生
    ↓
任务被唤醒

而不是所有任务都不断查询状态。

四、优化任务栈大小

创建任务时:

复制代码
xTaskCreate(
    SensorTask,
    "Sensor",
    1024,
    NULL,
    2,
    NULL
);

如果SensorTask实际上只需要很少的栈空间,那么:

复制代码
1024

就可能设置得过大。

STM32F407中StackType_t通常为:

复制代码
4Byte

因此:

复制代码
1024个StackType_t

大约就是:

复制代码
4096Byte

如果创建:

复制代码
5个任务

每个都随手给很大的任务栈,就会浪费大量SRAM。

可以使用:

复制代码
uxTaskGetStackHighWaterMark(
    SensorTaskHandle
);

观察任务运行过程中最小剩余栈空间。

例如:

复制代码
任务栈设置512

长期运行以后

High Water Mark = 350

说明当前任务栈可能存在较大的富余。

可以在充分测试的基础上适当减小,但一定要:

复制代码
保留安全余量。

不要为了省几十Byte RAM,把任务栈压到刚刚够用。

五、优化Heap使用

动态创建:

复制代码
任务

队列

信号量

互斥量

软件定时器

都会消耗FreeRTOS管理的内存。

可以查看:

复制代码
xPortGetFreeHeapSize();

以及:

复制代码
xPortGetMinimumEverFreeHeapSize();

例如:

复制代码
printf(
    "Free Heap = %lu\r\n",
    (unsigned long)
    xPortGetFreeHeapSize()
);

printf(
    "Min Heap = %lu\r\n",
    (unsigned long)
    xPortGetMinimumEverFreeHeapSize()
);

如果系统运行以后发现:

复制代码
大量Heap始终没有使用

可以根据实际情况调整配置。

如果Heap非常紧张,则需要检查:

复制代码
任务栈是不是设置太大

队列长度是不是过长

创建的任务是不是过多

而不是直接:

复制代码
继续增加configTOTAL_HEAP_SIZE。

六、任务不是越多越好

假设系统需要:

复制代码
LED1闪烁

LED2闪烁

LED3闪烁

最直接的方法可能是创建:

复制代码
LED1Task

LED2Task

LED3Task

当然可以运行。

但是每创建一个任务,都需要:

复制代码
TCB

任务栈

调度资源

如果这些LED逻辑非常简单,有时可以使用:

复制代码
一个任务

统一管理:

复制代码
void LedTask(void *pvParameters)
{
    while(1)
    {
        LED_Process();

        vTaskDelay(
            pdMS_TO_TICKS(10)
        );
    }
}

所以:

复制代码
一个功能

并不一定必须对应:

复制代码
一个任务。

是否需要独立任务,要看它是否具有:

复制代码
独立执行周期

独立阻塞条件

不同实时性要求

七、选择合适的任务通信方式

FreeRTOS提供很多通信和同步工具:

复制代码
Queue

Semaphore

Mutex

Event Group

Task Notification

它们并不是:

复制代码
哪个高级就用哪个。

例如:

复制代码
需要传递一组数据
        ↓
Queue

ISR通知任务处理事件
        ↓
Task Notification
或二值信号量

保护UART、SPI等共享资源
        ↓
Mutex

等待多个状态位
        ↓
Event Group

如果只是:

复制代码
Task1通知Task2一次

却专门创建一个很大的队列,就可能没有必要。

选择最符合需求的机制,程序通常也会更加清晰。

八、优先级优化不是全部调高

假设:

复制代码
ControlTask    4

SensorTask     3

UartTask       2

LedTask        1

这样的优先级体现了不同任务的实时性要求。

但是如果把所有任务都设置成:

复制代码
最高优先级

并不会让系统整体变快。

相反,优先级设计不合理可能造成:

复制代码
低优先级任务长时间得不到运行

任务切换增加

程序逻辑变得难以分析

所以任务优先级优化的核心不是:

复制代码
提高优先级

而是:

复制代码
让真正需要快速响应的任务

拥有更合适的优先级。

九、减少不必要的printf

调试时我们经常写:

复制代码
printf("Task1 Running\r\n");

如果放在:

复制代码
while(1)

中高频执行,串口输出本身就可能占用大量时间。

例如:

复制代码
while(1)
{
    printf("Sensor Task Running\r\n");

    ReadSensor();

    vTaskDelay(
        pdMS_TO_TICKS(10)
    );
}

每10ms打印一次:

复制代码
1秒约100次

调试时可能没有问题,但正式运行时这些输出往往没有必要。

因此项目完成以后,可以删除或降低:

复制代码
高频调试打印。

否则有时候你看到的:

复制代码
系统性能差

其实大量时间都消耗在串口输出上。

十、总结

这一节学习了FreeRTOS几个比较实用的性能优化方向。

首先要记住:

复制代码
不要凭感觉优化

↓

先测量

↓

再修改

可以通过:

复制代码
uxTaskGetStackHighWaterMark();

检查任务栈。

通过:

复制代码
xPortGetFreeHeapSize();

xPortGetMinimumEverFreeHeapSize();

检查Heap。

通过运行时间统计观察:

复制代码
哪些任务占用了大量CPU。

实际优化时重点检查:

复制代码
任务是否存在无意义死循环

能不能通过阻塞等待事件

任务栈是否设置过大

Heap是否合理

任务数量是否过多

任务通信方式是否合适

任务优先级是否合理

是否存在大量无意义printf

FreeRTOS性能优化并不是:

复制代码
让CPU一直高速运行。

更合理的目标应该是:

复制代码
该运行的任务及时运行

不需要运行的任务及时阻塞

RAM不浪费

CPU不空转

任务之间关系清楚

做到这些以后,一个FreeRTOS工程通常就已经具备了比较好的运行效率和可维护性。

相关推荐
01_ice1 小时前
前端学习css
前端·css·学习
for_ever_love__3 小时前
PySpark学习: 数据输出
开发语言·python·学习·spark
liulilittle4 小时前
llmx 学习手册 06 —— CPU 指令集优化(AVX-512 三层演进)
c++·学习·算法·ai·llm
山甫aa4 小时前
JavaWeb后端开发学习手册
java·开发语言·数据库·学习·mysql·springboot·web
网络设计ensp5 小时前
商超网络设计
网络·学习·安全·智能路由器·ensp·华为网络设计
边境悍匪5 小时前
蜗牛学苑 Java 智能体学习 Day26|HTML(续)、断点调试思维导图复盘
学习
liuyicenysabel15 小时前
多服务上线日记三:
运维·服务器·笔记·学习
YM52e15 小时前
鸿蒙ArkTS实战项目 - 门店陈列巡检台:巡检卡片与多列切换实现
学习·华为·harmonyos
一只小小的芙厨16 小时前
基础数论总结
笔记·学习·算法