查看: 1050|回复: 1

[Arduino] ESP32 Arduino教程:FreeRTOS队列性能测试

[复制链接]
在这个esp32 Arduino教程中,我们将对从FreeRTOS队列插入和消费项的性能进行简单分析。测试使用的是一个集成在ESP32开发板中的DFRobotFireBeetle ESP32模块设备。


介绍
在这篇文章中,我们将简单地分析从FreeRTOS队列中插入和使用队列项的性能。请注意,先前文章:ESP32 Arduino教程:使用FreeRTOS队列进行任务间通信 中介绍了如何利用FreeRTOS队列进行任务间通信,在这篇文字中我们将使用完全相同的代码,尽管我们现在要执行的是时间测量任务。
我们将使用millis函数(https://github.com/espressif/ard ... sp32-hal-misc.c#L48)来获得时间测量值。这个函数在其执行过程中使用了FreeRTOSxTaskGetTickCount函数(http://www.freertos.org/a00021.html#xTaskGetTickCount)。
请注意,micros函数(https://www.arduino.cc/en/Reference/Micros)可用于更精确地测量时间,但在撰写本文时,我在使用此函数时遇到了一些问题。因此,我使用了millis函数,虽然它的精确度不如micros函数,但其结果足够实用。
值得一提的是,这将是一个用于检验FreeRTOS队列如何处理插入和使用大量数据的简单测试,因此我们的示例可能不是队列的典型使用示例。此外,我们用于测量性能的方法并不是最精确的,因为我们的目标只是粗略地了解执行操作所需的时间。

代码
我们需要首先声明一个QueueHandle_t类型的全局变量,它将是我们的任务句柄。我们还将把给定时间内队列项的最大数量赋予一个全局变量,它将在随后用于初始化任务。这样,如果想要测试不同大小的队列,我们就可以轻松地控制队列的大小。
我们还将存储6个变量,它们用于计算队列插入和消耗,其中一个变量用于记录每个操作的开始和结束时间,另一个用于计算开始与结束时的时间差,它对应于程序执行时间。

[AppleScript] 纯文本查看 复制代码
unsigned long startProducing, endProducing, startConsuming, endConsuming, producingTime, consumingTime;

 

setup函数将首先对串口进行初始化,然后初始化队列。为简单起见,我们将使用整数队列,其最大规模等于全局变量queueSize指定的大小。
在创建队列之后进行错误检查,只是为了确认是否可以创建这个队列。了解如何创建及使用FreeRTOS队列的详细指南: ESP32 Arduino教程:FreeRTOS队列

[AppleScript] 纯文本查看 复制代码
Serial.begin(112500);

queue = xQueueCreate( queueSize, sizeof( int ) );

if(queue == NULL){

  Serial.println("Error creating the queue");

}

在创建队列之后,我们将创建producer和consumer任务。点击此处了解如何在Arduino内核上使用FreeRTOS任务的教程。我们稍后将指定任务函数。

[AppleScript] 纯文本查看 复制代码
xTaskCreate(

                    producerTask,     /* Task function. */

                    "Producer",       /* String with name of task. */

                    10000,            /* Stack size in words. */

                    NULL,             /* Parameter passed as input of the task */

                    10,               /* Priority of the task. */

                    NULL);            /* Task handle. */

xTaskCreate(

                    consumerTask,     /* Task function. */

                    "Consumer",       /* String with name of task. */

                    10000,            /* Stack size in words. */

                    NULL,             /* Parameter passed as input of the task */

                    10,               /* Priority of the task. */

                    NULL);            /* Task handle. */

请注意,两个任务在创建时的优先级均为10,两者中哪个任务的优先级高于setup函数任务,那么它的优先级将被赋值为1,关于优先级的教程:ESP32 Arduino:获取FreeRTOS任务优先级 。因此,以下代码只应在两个任务都结束后执行,只要它们不阻碍调用函数即可。
因此,setup函数的余下代码将负责计算生成和使用队列项所花费的时间,并将这些值打印出来。当然,时间计算是通过将操作结束时的时间减去操作开始的时间来实现的,我们将在每个任务中都对此进行了设置。

[AppleScript] 纯文本查看 复制代码
producingTime = endProducing - startProducing;

Serial.println(producingTime);

 

consumingTime = endConsuming - startConsuming;

Serial.println(consumingTime);

现在,我们将编写任务函数代码。与之前的教程:ESP32 Arduino教程:使用FreeRTOS队列进行任务间通信 相同 ,我们只需调用millis函数来获取操作的开始和结束时间。


[AppleScript] 纯文本查看 复制代码
void producerTask( void * parameter )
{
    startProducing = millis();
 
    for( int i = 0;i<queueSize;i++ ){
      xQueueSend(queue, &i, portMAX_DELAY);
    }
 
    endProducing = millis();
 
    vTaskDelete( NULL );
 
}
 
void consumerTask( void * parameter)
{
    startConsuming = millis();
 
    int element;
 
    for( int i = 0;i<queueSize;i++ ){
        xQueueReceive(queue, &element, portMAX_DELAY);
    }
 
    endConsuming = millis();
 
    vTaskDelete( NULL );
 
}


最终的源代码如下所示。请注意,您可以尝试使用不同的队列大小来检验不同的执行时间。为了提高易读性,代码中还添加了一些额外的打印显示代码。

[AppleScript] 纯文本查看 复制代码
QueueHandle_t queue;
int queueSize = 10000;
unsigned long startProducing, endProducing, startConsuming, endConsuming, producingTime, consumingTime; 
 
void setup() {
 
  Serial.begin(112500);
 
  queue = xQueueCreate( queueSize, sizeof( int ) );
 
  if(queue == NULL){
    Serial.println("Error creating the queue");
  }
 
  xTaskCreate(
                    producerTask,     /* Task function. */
                    "Producer",       /* String with name of task. */
                    10000,            /* Stack size in words. */
                    NULL,             /* Parameter passed as input of the task */
                    10,               /* Priority of the task. */
                    NULL);            /* Task handle. */
 
  xTaskCreate(
                    consumerTask,     /* Task function. */
                    "Consumer",       /* String with name of task. */
                    10000,            /* Stack size in words. */
                    NULL,             /* Parameter passed as input of the task */
                    10,               /* Priority of the task. */
                    NULL);            /* Task handle. */
 
    producingTime = endProducing - startProducing;
    Serial.print("Producing time: ");
    Serial.println(producingTime);
 
    consumingTime = endConsuming - startConsuming;
    Serial.print("Consuming time: ");
    Serial.println(consumingTime);
}
 
void loop() {
  delay(100000);
}
 
void producerTask( void * parameter )
{
    startProducing = millis();
 
    for( int i = 0;i<queueSize;i++ ){
      xQueueSend(queue, &i, portMAX_DELAY);
    }
 
    endProducing = millis();
 
    vTaskDelete( NULL );
 
}
 
void consumerTask( void * parameter)
{
    startConsuming = millis();
 
    int element;
 
    for( int i = 0; i<queueSize; i++ ){
        xQueueReceive(queue, &element, portMAX_DELAY);
    }
 
    endConsuming = millis();
 
    vTaskDelete( NULL );
 
}




测试代码
为了测试代码,您只需简单地将其上传到ESP32开发板并打开串口监视器即可。您可以看到类似于图1的输出结果,它显示了这两个操作的执行时间。


1.png

图1 - FreeRTOS队列性能分析程序的输出结果


图1中显示了queueSize为10000个整数时的测试结果,这个测试规模很大。从结果可以看出,生产和消耗时间大致相同(大约30毫秒),考虑到队列是线程安全的,所以这是一个很好的值,我们不需要担心竞态条件(racing condition)。

当然,对于一个真实场景而言,使用队列为一个任务生成大量元素并在之后通过另一个任务消耗队列项可能并不是最佳的解决方案,因为这可以通过更简单的数据结构来实现。
尽管如此,我们的目标只是研究如何使用队列处理这类含大量数据的操作,其表现又将如何。


注:本文作者是Nuno Santos,他是一位和蔼可亲的电子和计算机工程师,住在葡萄牙里斯本 (Lisbon)。
他写了很多有关ESP32、ESP8266的有用的教程和项目。

查看更多ESP32/ESP8266教程和项目,请点击 : ESP32教程汇总贴
英文版教程 : ESP32 tutorial

gada888  版主

发表于 2019-1-14 18:56:10

支持哈
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
wifi气象站

硬件清单

btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4

© 2013-2019 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail