ESP-IDFを使ってみる

xEventGroupとxQueueの性能


ESP-IDFでマルチタスクを構築する場合、タスク間で非同期のイベント通知を行うことが良くあります。
FreeRTOSではイベント通知のためにxEventGroupが使われますが、非同期(いつ来るか分からない)イベントであれば
xQueueでも同じことができます。

そこで、xEventGroupとxQueueのパフォーマンスを比較してみました。
xEventGroupではxEventGroupWaitBits()で特定のビットがONになるのをタイムアウト無しで待つやり方と、
xEventGroupGetBits()でイベントの値を取ってからビットを判定するやり方が有りますが、両方を試しました。

使用したソースは以下のコードです。
xEventGroupもxQueueもどちらも100,000回、イベントが有るかどうかを確認します。
当然イベントを発行するタスクはいないので、全て空振りになります。
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "freertos/queue.h"
#include "esp_log.h"

//#define EVENTWAIT
#define EVENTGET
//#define QUEUE

#define QUEUE_LENGTH            10
#define QUEUE_SIZE              10
#define DATA_SIZE               100

#define WAIT_BIT                BIT1

static const char *TAG = "MAIN";

TaskHandle_t xTask1 = NULL, xTask2 = NULL;
QueueHandle_t xQueue;
EventGroupHandle_t xEvent;

void task1(void *pvParameter)
{
        ESP_LOGI(pcTaskGetTaskName(0), "Start");
        TickType_t startTick = 0;
        TickType_t endTick = 0;
        TickType_t diffTick = 0;
        uint32_t counter = 0;

        startTick = xTaskGetTickCount();
        for(uint32_t i=0;i<100000;i++) {
                counter++;
                EventBits_t bits = xEventGroupWaitBits(xEvent,
                        WAIT_BIT,
                        pdFALSE,
                        pdFALSE,
                        0);
        }
        endTick = xTaskGetTickCount();
        diffTick = endTick - startTick;
        ESP_LOGI(pcTaskGetTaskName(0), "elapsed time[ms]:%d counter:%d",diffTick*portTICK_RATE_MS, counter);

        while(1) {
          vTaskDelay(100);
        }
}

void task2(void *pvParameter)
{
        ESP_LOGI(pcTaskGetTaskName(0), "Start");
        TickType_t startTick = 0;
        TickType_t endTick = 0;
        TickType_t diffTick = 0;
        uint32_t counter = 0;

        startTick = xTaskGetTickCount();
        for(uint32_t i=0;i<100000;i++) {
                counter++;
                EventBits_t bits = xEventGroupGetBits(xEvent);
        }
        endTick = xTaskGetTickCount();
        diffTick = endTick - startTick;
        ESP_LOGI(pcTaskGetTaskName(0), "elapsed time[ms]:%d counter:%d",diffTick*portTICK_RATE_MS, counter);

        while(1) {
          vTaskDelay(100);
        }
}


void task11(void *pvParameter)
{
        ESP_LOGI(pcTaskGetTaskName(0), "Start");
        TickType_t startTick = 0;
        TickType_t endTick = 0;
        TickType_t diffTick = 0;
        uint32_t counter = 0;

        startTick = xTaskGetTickCount();
        char rx_item[DATA_SIZE];
        while (1) {
                //Receive an item from no-split ring buffer
                BaseType_t res = xQueueReceive(xQueue, rx_item, 0);
                counter++;
                if (counter == 100000) {
                        endTick = xTaskGetTickCount();
                        diffTick = endTick - startTick;
                        ESP_LOGI(pcTaskGetTaskName(0), "elapsed time[ms]:%d counter:%d",diffTick*portTICK_RATE_MS, counter);
                }
        }


}

void app_main()
{
#ifdef EVENTWAIT
        /* Create EventGroup */
        ESP_LOGI(TAG, "xEventGroupCreate");
        xEvent = xEventGroupCreate();
        configASSERT( xEvent );

        /* Create task */
        xTaskCreate(task1, "EventWait", 4096, NULL, 1, &xTask1);
#endif

#ifdef EVENTGET
        /* Create EventGroup */
        ESP_LOGI(TAG, "xEventGroupCreate");
        xEvent = xEventGroupCreate();
        configASSERT( xEvent );

        /* Create task */
        xTaskCreate(task2, "EventGet", 4096, NULL, 1, &xTask1);
#endif

#ifdef QUEUE
        /* Create Queue */
        ESP_LOGI(TAG, "xQueueCreate");
        xQueue = xQueueCreate( QUEUE_LENGTH, QUEUE_SIZE );
        configASSERT( xQueue );

        /* Create task */
        xTaskCreate(task11, "QueueReceive", 4096, NULL, 1, &xTask1);

#endif

}

結果は以下の様になりました。

I (754) EventWait: elapsed time[ms]:440 counter:100000

I (444) EventGet: elapsed time[ms]:130 counter:100000

I (504) QueueReceive: elapsed time[ms]:190 counter:100000

一番早いのは、xEventGroupGetBits()でイベントの値を取り出して、プログラム内部でビットの状態を判定する方法でした。
但し、xEventGroupGetBits()では待ち時間を指定することができないので、CPU資源をたくさん消費します。
マルチタスクの環境で、1つのタスクが待ち時間無しでループすることは御法度です。

xEventGroupWaitBits も xQueueReceive も、待ち時間を指定して、イベントが来るまで待つことができます。
xQueueReceiveの方が2倍ほど高速に動きます。



こ ちらにFreeRTOSのチュートリアルが公開されています。
EventBits_t の大きさについて、以下の記述が有ります。

More About the EventBits_t Data Type
The number of event bits in an event group is dependent on the configUSE_16_BIT_TICKS
compile time configuration constant within FreeRTOSConfig.h1

If configUSE_16_BIT_TICKS is 1, then each event group contains 8 usable event bits.
If configUSE_16_BIT_TICKS is 0, then each event group contains 24 usable event bits.

実際に確認してみましたが、ESP-IDFではconfigUSE_16_BIT_TICKS=0で定義されています。
EventBits には24bitsの値を格納することができます。

続く....