продолжение темы RTOS, начало см.
https://habr.com/ru/posts/1060466/
https://habr.com/p/1062346/

Получив результат, а именно, запуск SMP версии RTOS, задался вопросом, а какая же плата в виде ухудшения временнЫх характеристик взимается за межъядерное взаимодействие сервисов RTOS?

Достал из пыльного чулана давно написанный тест производительнсти сервисов RTOS и причесал с целью использования в одно‑ и много‑ядерном режиме.

UPDATE: особая благодарность пользователю @Brazilуказавшему на недочет в тесте, связанный с приоритетами задач, результаты заменены.

спойлер:

os_perf_test.c:

#include "..\H\typedef.h"
#include "RTT\RTT_ex.h"

#include "osal.h"
#include "osal_api.h"

#include "os_perf_timer.h"

//perfomance test for OS

//config: ============================================================
//число проходов для усреднения результатов
#define TEST_PASS_NUM           64

//включение тестов конкретных сервисов OS
#define PERFTEST_QUEUE
#define PERFTEST_EVF
#define PERFTEST_SEM
#define PERFTEST_MTX

//режимы сбора статистики производительности
// разрешить счетчики PMU
#define PMU_MODE_ENABLE
// разрешить таймер Cortex-A7
#define TRM_MODE_ENABLE
// разрешить дополнительные метрики PMU
#define PMU_METRIC_ENABLE


//частота таймера A7
#define TRM_FREQ_MHZ    24

//запускать на разных ядрах
//#define DIFFERENT_CORES

//config: ============================================================

//взаимоблокировки конфигурации
#ifdef MPCORE_MODE
  #ifdef DIFFERENT_CORES
    //замер только таймером
    #undef PMU_MODE_ENABLE
    #define TRM_MODE_ENABLE
  #endif
#endif

#ifndef PMU_MODE_ENABLE
  #undef PMU_METRIC_ENABLE
#endif


extern DWORD getCPU_clk(void);
extern DWORD OS_BSP_getCPUClock(void);
extern WORD dig_itoa(BYTE *dst,int v,DWORD off,BYTE i,BYTE f,BYTE signMode);


enum {PERF_QU_SID,PERF_EVF_SID,PERF_SEM_SID,PERF_MTX_SID,PERF_SID_TOTAL};
enum _TEST_EVFS {QU_MEASURE=1,EVF_MEASURE=2,SEM_MEASURE=4,MTX_MEASURE=8};
#define EVF_PERFTEST 1

typedef struct _OS_PERF_DATA_ENTRY
{
DWORD c_min;            //pmu cycles
DWORD c_max;
DWORD c_total;
DWORD i_min;            //pmu instructions
DWORD i_max;
DWORD i_total;
DWORD t_min;            //trm ticks
DWORD t_max;
DWORD t_total;
DWORD pmu_M1_total;     //pmu metric1
DWORD pmu_M2_total;     //pmu metric2
}OS_PERF_DATA_ENTRY;

typedef struct _OS_PERF_DATA
{
DWORD pass;
OS_PERF_DATA_ENTRY pdEntry[PERF_SID_TOTAL];
}OS_PERF_DATA;

typedef struct _OS_PERF_COUNTERS
{
DWORD pmuM1[PERF_SID_TOTAL];
DWORD pmuM2[PERF_SID_TOTAL];
DWORD cyc[PERF_SID_TOTAL];
DWORD instr[PERF_SID_TOTAL];
QWORD tt[PERF_SID_TOTAL];
}OS_PERF_COUNTERS;



//RTOS objects -------------------------------------------
//#pragma default_variable_attributes = @ "DATA_NO_CACHE"
//#pragma default_variable_attributes = @ "SRAM"
#pragma default_variable_attributes=@ "RTOS_USERDATA"
// not correct
  //#define TEST1_PRIORITY        (PRIO_LVL 15)
  //#define TEST2_PRIORITY        (PRIO_LVL 16)

  #define TEST1_PRIORITY        (PRIO_LVL 16)
  #define TEST2_PRIORITY        (PRIO_LVL 15)
  #define TEST3_PRIORITY        (PRIO_LVL 17)

  #define TEST1_QUEUE_LEN       8
  #define TEST1_QUEUE_MSGSIZE   1

  #define TEST1_STACK_SIZE      128
  #define TEST2_STACK_SIZE      128
  #define TEST3_STACK_SIZE      128

  #pragma data_alignment=64
  OS_QUEUE_CB   TEST1Queue;
  #pragma data_alignment=64
  OS_EVENTFLAGS_GROUP_CB TEST1Evf;
  #pragma data_alignment=64
  OS_MUTEX_CB TEST1Mtx;
  #pragma data_alignment=64
  OS_SEMAPHORE_CB TEST1Sem;

  #pragma data_alignment=64
  OS_TASK_CB    TEST1Thread;
  #pragma data_alignment=64
  OS_TASK_CB    TEST3Thread;

  #pragma data_alignment=64
  DWORD TEST1QueueData[TEST1_QUEUE_LEN*TEST1_QUEUE_MSGSIZE];
  #pragma data_alignment=64
  OS_TASK_STACK TEST1ThreadStack[TEST1_STACK_SIZE];
  #pragma data_alignment=64
  OS_TASK_CB    TEST2Thread;
  #pragma data_alignment=64
  OS_TASK_STACK TEST2ThreadStack[TEST2_STACK_SIZE];
  #pragma data_alignment=64
  OS_TASK_STACK TEST3ThreadStack[TEST3_STACK_SIZE];
  #pragma data_alignment=64
  OS_EVENTFLAGS_GROUP_CB TEST1SrvEvf;

  //counters --------------
  #pragma data_alignment=64
  OS_PERF_COUNTERS cnt1;
  DWORD errcnt[PERF_SID_TOTAL];
  DWORD sid1;
  #pragma data_alignment=64
  OS_PERF_COUNTERS cnt2;
  DWORD sid2;
#pragma default_variable_attributes=


//counters -------------------------------------------------
#pragma default_variable_attributes = @ "DATA_NO_CACHE"
  /*
  //counters --------------
  #pragma data_alignment=64
  OS_PERF_COUNTERS cnt1;
  #pragma data_alignment=64
  OS_PERF_COUNTERS cnt2;
  DWORD sid2;
  #pragma data_alignment=64
  DWORD errcnt[PERF_SID_TOTAL];
  DWORD sid1;
  */
#pragma default_variable_attributes=


OS_PERF_DATA OSPerfData;
static void clearPerfData(void)
{
OSPerfData.pass=0;
for(DWORD i=0;i<PERF_SID_TOTAL;i++)
  {
  OSPerfData.pdEntry[i].c_min=0xFFFFFFFF;
  OSPerfData.pdEntry[i].c_max=0;
  OSPerfData.pdEntry[i].c_total=0;
  OSPerfData.pdEntry[i].i_min=0xFFFFFFFF;
  OSPerfData.pdEntry[i].i_max=0;
  OSPerfData.pdEntry[i].i_total=0;
  OSPerfData.pdEntry[i].t_min=0xFFFFFFFF;
  OSPerfData.pdEntry[i].t_max=0;
  OSPerfData.pdEntry[i].t_total=0;
  OSPerfData.pdEntry[i].pmu_M1_total=0;
  OSPerfData.pdEntry[i].pmu_M2_total=0;
  }
}


void srtrcpyfill(BYTE *dst,BYTE *src,DWORD len)
{
DWORD i,srclen=strlen((char *)src);
for(i=0;i<srclen;i++)dst[i]=src[i];
for(;i<len;i++)dst[i]=' ';
}


static BYTE *servName[PERF_SID_TOTAL]={"queue","eventFlag","semaphore","mutex"};
//                0         1         2         3         4         5         6         7
//                0.........0....5....0.2.......01......8.0.......8.0......7..0.....6...0...4
static BYTE *ms5="xxxxxxxxxxxx   Cortex-A7 TRM result             xx.xxx   xx.xxx   xx.xxx  \r\n";
#ifndef PMU_MODE_ENABLE
static BYTE *ms4="service_name                                    min[uS]  max[uS]  avg[uS] \r\n";
#endif
#ifdef PMU_MODE_ENABLE
static BYTE *ms3="PMU metrics    c1=xxxxxx       c2=yyyyyy \r\n";
static BYTE *ms2="xxxxxxxxxxxx   yyyyyy/zzzzzz   yyyyyy/zzzzzz    xx.xxx   xx.xxx   xx.xxx  xx.xxx  \r\n";
static BYTE *ms1="service_name   min[cyc/inst]   max[cyc/inst]    min[uS]  max[uS]  avg[uS] cpi     \r\n";
#endif
static BYTE *ms0=" CPUclk=XXXXMHz\r\n";

static void ShowPerfomanceDataHelper(OS_PERF_DATA *pd,DWORD sid,DWORD cpuclk,DWORD trmclk)
{
OS_PERF_DATA_ENTRY *pde=&pd->pdEntry[0];
#ifdef PMU_MODE_ENABLE
  srtrcpyfill(ms2,servName[sid],12);
  srtrcpyfill(ms5," ",12);
#else
  srtrcpyfill(ms5,servName[sid],12);
#endif

#ifdef PMU_MODE_ENABLE
  {
  DWORD avgc,avgi;

  avgc=pde[sid].c_total/pd->pass;
  avgi=pde[sid].i_total/pd->pass;
  //cycles/instructions by PMU -------------------------------------------------------
  dig_itoa(&ms2[15],pde[sid].c_min,0,6,0,0);ms2[15+6]='/';
  dig_itoa(&ms2[31],pde[sid].c_max,0,6,0,0);ms2[31+6]='/';
  dig_itoa(&ms2[22],pde[sid].i_min,0,6,0,0);ms2[22+6]=' ';
  dig_itoa(&ms2[38],pde[sid].i_max,0,6,0,0);ms2[38+6]=' ';
  //time by PMU ----------------------------------------------------------------------
  dig_itoa(&ms2[48],pde[sid].c_min*1000/cpuclk,0,2,3,0);ms2[48+6]=' ';
  dig_itoa(&ms2[57],pde[sid].c_max*1000/cpuclk,0,2,3,0);ms2[57+6]=' ';
  dig_itoa(&ms2[66],pde[sid].c_total*1000/cpuclk/pd->pass,0,2,3,0);ms2[66+6]=' ';
  //cpi by PMU -----------------------------------------------------------------------
  dig_itoa(&ms2[74],avgc*1000/avgi,0,2,3,0);ms2[74+6]=' ';
  RTT_WriteStringEx(ms2);
  }
#endif
#ifdef TRM_MODE_ENABLE
  //time by PMU ----------------------------------------------------------------------
  dig_itoa(&ms5[48],pde[sid].t_min*1000/trmclk,0,2,3,0);ms5[48+6]=' ';
  dig_itoa(&ms5[57],pde[sid].t_max*1000/trmclk,0,2,3,0);ms5[57+6]=' ';
  dig_itoa(&ms5[66],pde[sid].t_total*1000/trmclk/pd->pass,0,2,3,0);ms5[66+6]=' ';
  RTT_WriteStringEx(ms5);
#endif
#ifdef PMU_METRIC_ENABLE
  //PMU metrics
  dig_itoa(&ms3[18],pde[sid].pmu_M1_total/pd->pass,0,6,0,0);ms3[18+6]=' ';
  dig_itoa(&ms3[34],pde[sid].pmu_M2_total/pd->pass,0,6,0,0);ms3[34+6]=' ';
  RTT_WriteStringEx(ms3);
#endif
}


static void ShowPerfomanceData(BYTE *osname,OS_PERF_DATA *pd)
{
DWORD cpuclk=getCPU_clk()/1000;
//DWORD cpuclk=OS_BSP_getCPUClock()/1000;
DWORD trmclk=TRM_FREQ_MHZ;

dig_itoa(&ms0[8],cpuclk,0,4,0,0);ms0[8+4]='M';
RTT_WriteStringEx("\r\nPerfomance info ");
RTT_WriteStringEx(osname);
RTT_WriteStringEx(ms0);

#ifdef PMU_MODE_ENABLE
  RTT_WriteStringEx(ms1);
#else
  RTT_WriteStringEx(ms4);
#endif

#ifdef PERFTEST_QUEUE
  ShowPerfomanceDataHelper(pd,PERF_QU_SID,cpuclk,trmclk);
#endif
#ifdef PERFTEST_EVF
  ShowPerfomanceDataHelper(pd,PERF_EVF_SID,cpuclk,trmclk);
#endif
#ifdef PERFTEST_SEM
  ShowPerfomanceDataHelper(pd,PERF_SEM_SID,cpuclk,trmclk);
#endif
#ifdef PERFTEST_MTX
  ShowPerfomanceDataHelper(pd,PERF_MTX_SID,cpuclk,trmclk);
#endif
}


static void perf_test_prepare(DWORD sid)
{
#ifdef PMU_MODE_ENABLE
  #ifdef PMU_METRIC_ENABLE
    cnt1.pmuM1[sid]=OS_perfTimerGetCnt1();cnt2.pmuM1[sid]=cnt1.pmuM1[sid];
    cnt1.pmuM2[sid]=OS_perfTimerGetCnt2();cnt1.pmuM2[sid]=cnt1.pmuM2[sid];
  #endif
  cnt1.cyc[sid]=OS_perfTimerGetCycle();cnt2.cyc[sid]=cnt1.cyc[sid];
  cnt1.instr[sid]=OS_perfTimerGetCnt0();cnt2.instr[sid]=cnt1.instr[sid];
#endif
#ifdef TRM_MODE_ENABLE
  cnt1.tt[sid]=OS_perfA7TRM_ticks();cnt2.tt[sid]=cnt1.tt[sid];
#endif
}


static void perf_test_wrResult(DWORD sid)
{
#ifdef PMU_MODE_ENABLE
  {
  DWORD t;
  t=OS_perfTime(cnt1.cyc[sid],cnt2.cyc[sid]);
  if(t<OSPerfData.pdEntry[sid].c_min)OSPerfData.pdEntry[sid].c_min=t;
  if(t>OSPerfData.pdEntry[sid].c_max)OSPerfData.pdEntry[sid].c_max=t;
  OSPerfData.pdEntry[sid].c_total+=t;
  t=OS_perfTime(cnt1.instr[sid],cnt2.instr[sid]);
  if(t<OSPerfData.pdEntry[sid].i_min)OSPerfData.pdEntry[sid].i_min=t;
  if(t>OSPerfData.pdEntry[sid].i_max)OSPerfData.pdEntry[sid].i_max=t;
  OSPerfData.pdEntry[sid].i_total+=t;
  #ifdef PMU_METRIC_ENABLE
    t=OS_perfTime(cnt1.pmuM1[sid],cnt2.pmuM1[sid]);
    OSPerfData.pdEntry[sid].pmu_M1_total+=t;
    t=OS_perfTime(cnt1.pmuM2[sid],cnt2.pmuM2[sid]);
    OSPerfData.pdEntry[sid].pmu_M2_total+=t;
  #endif
  }
#endif
#ifdef TRM_MODE_ENABLE
  {
  QWORD t4;
  t4=OS_perfTime4(cnt1.tt[sid],cnt2.tt[sid]);
  if(t4<OSPerfData.pdEntry[sid].t_min)OSPerfData.pdEntry[sid].t_min=t4;
  if(t4>OSPerfData.pdEntry[sid].t_max)OSPerfData.pdEntry[sid].t_max=t4;
  OSPerfData.pdEntry[sid].t_total+=t4;
  }
#endif
}


static DWORD pass_error=0;
OS_TASK_FUNC Test1Entry(OS_TASK_PARM parm)
{//main test task
DWORD rc,qulen,semv;
OS_EVENTFLAGS_VALUE_T evf,evf2,evf3;
#ifdef PERFTEST_QUEUE
  DWORD qd=0xDEADBEEF;
#endif

RTT_WriteStringEx("PerfomanceTest task1 started! ----\r\n");
OS_task_sleep(500);
clearPerfData();
for(DWORD i=0;i<PERF_SID_TOTAL;i++){errcnt[i]=0;}

OS_perfTimerInit();
OS_perfTimerStart();
OS_mutex_get(&TEST1Mtx,100);
#ifdef ONE_2TH_TASK
  OS_task_resume(&TEST2Thread);
#endif

for(;;)
  {
  if(OSPerfData.pass==0)
    {
    RTT_WriteStringEx("\r\n\r\nPerfomance test started. Wait..\r\n");
    sid1=0xFFFF;
    OS_task_sleep(200);
    }
  OS_task_sleep(1);

#ifdef PERFTEST_QUEUE
  //queue
  sid1=PERF_QU_SID;
  perf_test_prepare(sid1);
  OS_queue_send(&TEST1Queue,&qd,100);
  rc=OS_evf_get(&TEST1SrvEvf,QU_MEASURE,OS_EVF_OR,OS_EVF_NO_CLEAR,&evf,OS_WAIT_FOREVER);
  if(evf&QU_MEASURE)
    {perf_test_wrResult(sid1);}
  else
    {errcnt[sid1]++;}
#endif
#ifdef PERFTEST_EVF
  //evf
  sid1=PERF_EVF_SID;
  perf_test_prepare(sid1);
  OS_evf_set(&TEST1Evf,EVF_PERFTEST);
  rc=OS_evf_get(&TEST1SrvEvf,EVF_MEASURE,OS_EVF_OR,OS_EVF_NO_CLEAR,&evf,OS_WAIT_FOREVER);
  if(evf&EVF_MEASURE)
    {perf_test_wrResult(sid1);}
  else
    {errcnt[sid1]++;}
#endif
#ifdef PERFTEST_SEM
  //sem
  sid1=PERF_SEM_SID;
  perf_test_prepare(sid1);
  OS_sem_put(&TEST1Sem);
  rc=OS_evf_get(&TEST1SrvEvf,SEM_MEASURE,OS_EVF_OR,OS_EVF_NO_CLEAR,&evf,OS_WAIT_FOREVER);
  if(evf&SEM_MEASURE)
    {perf_test_wrResult(sid1);}
  else
    {errcnt[sid1]++;}
#endif
#ifdef PERFTEST_MTX
  //mtx
  sid1=PERF_MTX_SID;
  perf_test_prepare(sid1);
  OS_mutex_put(&TEST1Mtx);
  rc=OS_evf_get(&TEST1SrvEvf,MTX_MEASURE,OS_EVF_OR,OS_EVF_NO_CLEAR,&evf,OS_WAIT_FOREVER);
  if(evf&MTX_MEASURE)
    {perf_test_wrResult(sid1);}
  else
    {errcnt[sid1]++;}
  OS_mutex_get(&TEST1Mtx,OS_WAIT_FOREVER);
#endif
  //проверка состояния задач после прохода  ----
  OS_task_sleep(1);
  OS_evf_poll(&TEST1SrvEvf,(QU_MEASURE|EVF_MEASURE|SEM_MEASURE|MTX_MEASURE),&evf2);
  OS_evf_poll(&TEST1Evf,EVF_PERFTEST,&evf3);
  semv=OS_sem_getValue(&TEST1Sem);
  qulen=OS_queue_getlen(&TEST1Queue);
  if(evf2!=(QU_MEASURE|EVF_MEASURE|SEM_MEASURE|MTX_MEASURE) ||
     evf3!=0 || semv!=0 || qulen!=0)
    {
    pass_error++;
    }
  OS_evf_clr(&TEST1SrvEvf,(QU_MEASURE|EVF_MEASURE|SEM_MEASURE|MTX_MEASURE));
  //-------------------------------------------
  OSPerfData.pass++;
  if(OSPerfData.pass>=TEST_PASS_NUM)
    {
    ShowPerfomanceData("threadX",&OSPerfData);
    OS_task_sleep(2000);
    clearPerfData();
    OSPerfData.pass=0;
    }
  }
}


static void perf_test_fixCnt(DWORD sid)
{
#ifdef PMU_MODE_ENABLE
  cnt2.cyc[sid]=OS_perfTimerGetCycle();
  cnt2.instr[sid]=OS_perfTimerGetCnt0();
  #ifdef PMU_METRIC_ENABLE
    cnt2.pmuM1[sid]=OS_perfTimerGetCnt1();
    cnt2.pmuM2[sid]=OS_perfTimerGetCnt2();
  #endif
#endif
#ifdef TRM_MODE_ENABLE
  cnt2.tt[sid]=OS_perfA7TRM_ticks();
#endif
}


OS_TASK_FUNC Test2Entry(OS_TASK_PARM parm)
{
DWORD qd;
OS_EVENTFLAGS_VALUE_T evf;

RTT_WriteStringEx("PerfomanceTest task2 started! ----\r\n");
for(;;)
  {
  #ifdef PERFTEST_QUEUE
    OS_queue_receive(&TEST1Queue,&qd,OS_WAIT_FOREVER);
    perf_test_fixCnt(PERF_QU_SID);
    OS_evf_set(&TEST1SrvEvf,QU_MEASURE);
  #endif
  #ifdef PERFTEST_EVF
    sid2=PERF_EVF_SID;
    OS_evf_get(&TEST1Evf,EVF_PERFTEST,OS_EVF_OR,OS_EVF_NO_CLEAR,&evf,OS_WAIT_FOREVER);
    perf_test_fixCnt(PERF_EVF_SID);
    OS_evf_clr(&TEST1Evf,EVF_PERFTEST);
    OS_evf_set(&TEST1SrvEvf,EVF_MEASURE);
  #endif
  #ifdef PERFTEST_SEM
    sid2=PERF_SEM_SID;
    OS_sem_get(&TEST1Sem,OS_WAIT_FOREVER);
    perf_test_fixCnt(PERF_SEM_SID);
    OS_evf_set(&TEST1SrvEvf,SEM_MEASURE);
  #endif
  #ifdef PERFTEST_MTX
    sid2=PERF_MTX_SID;
    OS_mutex_get(&TEST1Mtx,OS_WAIT_FOREVER);
    perf_test_fixCnt(PERF_MTX_SID);
    OS_mutex_put(&TEST1Mtx);
    OS_evf_set(&TEST1SrvEvf,MTX_MEASURE);
  #endif
  }
}


OS_TASK_FUNC Test3Entry(OS_TASK_PARM parm)
{
RTT_WriteStringEx("PerfomanceTest task3 started! ----\r\n");

for(;;)
  {
  //volatile DWORD n;
  //for(n=0;n<100000000;n++){/*empty*/}
  OS_task_sleep(2000);
  //RTT_WriteStringEx("task3 loop..\r\n");
  }
}


void TEST1_DevInit(void)
{
sid1=0xDEAD;sid2=0xBEEF;
OS_evf_create(&TEST1SrvEvf,"evf_SrvTest1");
OS_evf_create(&TEST1Evf,"evf_Test1");
OS_mutex_create(&TEST1Mtx,"mtx_Test1");
OS_sem_create(&TEST1Sem,"sem_Test1",10,0);

OS_queue_create(&TEST1Queue,"qu_TEST1",TEST1_QUEUE_MSGSIZE,
                (OS_QUEUE_DATABUF_T)TEST1QueueData,TEST1_QUEUE_LEN);

OS_task_create(&TEST1Thread,"th_TEST1",Test1Entry,
               (OS_TASK_PARM)0, //start thread parm value
               TEST1ThreadStack,TEST1_STACK_SIZE,
               TEST1_PRIORITY,TEST1_PRIORITY,OS_NO_TIMESLICE,OS_TH_AUTOSTART);

OS_task_create(&TEST2Thread,"th_TEST2",Test2Entry,
               (OS_TASK_PARM)0, //start thread parm value
               TEST2ThreadStack,TEST2_STACK_SIZE,
               TEST2_PRIORITY,TEST2_PRIORITY,OS_NO_TIMESLICE,OS_TH_DONTSTART);

#ifdef MPCORE_MODE
  OS_task_create(&TEST3Thread,"th_TEST3",Test3Entry,
                 (OS_TASK_PARM)0, //start thread parm value
                 TEST3ThreadStack,TEST3_STACK_SIZE,
                 TEST3_PRIORITY,TEST3_PRIORITY,OS_NO_TIMESLICE,OS_TH_AUTOSTART);

  OS_task_smp_core_exclude(&TEST1Thread,AFF_MASK_CORE0_ONLY);

  #ifndef DIFFERENT_CORES
    #define TH2_AFFINITY_MASK     AFF_MASK_CORE0_ONLY
  #else
    #define TH2_AFFINITY_MASK     AFF_MASK_CORE1_ONLY
  #endif

  OS_task_smp_core_exclude(&TEST2Thread,TH2_AFFINITY_MASK);
  OS_task_smp_core_exclude(&TEST3Thread,TH2_AFFINITY_MASK);
#endif
}

os_perf_timer_pmu.c:

#include "..\h\typedef.h"


#include "DBGUTIL\pmu\sys_pmu.h"
#include "os_perf_timer.h"


extern DWORD OS_BSP_getPMU0Address(void);
extern DWORD getCPUID(void);


static DWORD pmuInited=0;
void OS_perfTimerInit(void)
{
_pmuInit_();
_pmuSetCountEvent_(0,PMU_INST_ARCH_EXECUTED);

_pmuSetCountEvent_(1,0x10); //
_pmuSetCountEvent_(2,0x12); //0x68

pmuInited=1;
}


void OS_perfTimerStop(void)
{
_pmuStopCounters_(pmuCYCLE_COUNTER|pmuCOUNTER0|pmuCOUNTER1|pmuCOUNTER2);
}


DWORD OS_perfTimerGetCycle(void)
{
return(_pmuGetCycleCount_());
}


DWORD OS_perfTimerGetCnt0(void)
{
return(_pmuGetEventCount_(0));
}


DWORD OS_perfTimerGetCnt1(void)
{
return(_pmuGetEventCount_(1));
}


DWORD OS_perfTimerGetCnt2(void)
{
return(_pmuGetEventCount_(2));
}


void OS_perfTimerStart(void)
{
if(!pmuInited){_pmuInit_();}
_pmuResetCounters_();
_pmuStartCounters_(pmuCYCLE_COUNTER|pmuCOUNTER0|pmuCOUNTER1|pmuCOUNTER2);
}


DWORD pmuerr=0;
DWORD OS_perfTime(DWORD t1,DWORD t2)
{
DWORD t;
if(t2>=t1)
  {t=t2-t1;}
else
  {t=t2+(0xFFFFFFFF-t1);}
return(t);
}


DWORD OS_perfTime4(QWORD t1,QWORD t2)
{
DWORD t;

if(t2>=t1)
  {t=t2-t1;}
else
  {t=t2+(0xFFFFFFFFFFFFFFFF-t1);}
return(t);
}


QWORD OS_perfA7TRM_ticks(void)
{
DWORD low,high;
asm("MRRC p15, 0, %0, %1, c14" : "=r"(low), "=r"(high));
return(((QWORD)high<<32)|low);
}

Здесь используются врапперы OSAL (OS Abstraction Layer), написанные много лет назад для безболезненной замены rtos в проекте. Тот проект проделал путь threadx→freertos→embos по определенным причинам, не относящимся к теме данной статьи.

osal.h:

#include "..\h\typedef.h"
//select one from list ---


#ifdef USE_EMBOS
  #define PRIO_LVL 255-
#else
  #ifdef USE_FREERTOS
    #define PRIO_LVL 31-
  #else
    #ifdef USE_THREADX
      #define PRIO_LVL 0+
    #endif
  #endif
#endif


#ifdef USE_EMBOS
  #include "embOS_AL.h"
#else
  #ifdef USE_FREERTOS
    #include "freeRTOS_AL.h"
  #else
    #ifdef USE_THREADX
      #include "threadX_AL.h"
    #endif
  #endif
#endif

А теперь плавно переходим к результатам. Тесты проведены на Allwinner T113, включены кэши L1-L2 и MMU.

одноядерная версия threadX:

Clk: CPU=1008000KHz   DRAM=0792000KHz  AXI=504000KHz
  PERIPH=600000KHz
     AHB=200000KHz
    APB0=100000KHz     APB1=024000KHz

Average: 64 passes

T133 Uni DRAM ===================================================
Perfomance info threadX CPUclk=1008MHz
service_name   min[cyc/inst]   max[cyc/inst]    min[uS]  max[uS]  avg[uS] cpi
queue          000324/000165   000548/000165    00.321   00.543   00.346  02.115
               Cortex-A7 TRM result             00.333   00.583   00.364
PMU metrics    c1=000006       c2=000017
eventFlag      000375/000187   000467/000187    00.372   00.463   00.381  02.053
               Cortex-A7 TRM result             00.375   00.500   00.387
PMU metrics    c1=000008       c2=000018
semaphore      000282/000153   000362/000153    00.279   00.359   00.288  01.895
               Cortex-A7 TRM result             00.291   00.375   00.302
PMU metrics    c1=000002       c2=000015
mutex          000351/000201   000613/000201    00.348   00.608   00.376  01.885
               Cortex-A7 TRM result             00.333   00.625   00.401
PMU metrics    c1=000006       c2=000026

SMP версия threadX, тестовые задачи запущены на одном ядре, второе простаивает:

Clk: CPU=1008000KHz   DRAM=0792000KHz  AXI=504000KHz
  PERIPH=600000KHz
     AHB=200000KHz
    APB0=100000KHz     APB1=024000KHz

Average: 64 passes

T133 Smp DRAM =======================================
Perfomance info threadX CPUclk=1008MHz
service_name   min[cyc/inst]   max[cyc/inst]    min[uS]  max[uS]  avg[uS] cpi
queue          001069/000374   001479/000374    01.060   01.467   01.103  02.973
               Cortex-A7 TRM result             01.083   01.458   01.116
PMU metrics    c1=000019       c2=000045
eventFlag      001412/000441   001540/000441    01.400   01.527   01.413  03.229
               Cortex-A7 TRM result             01.416   01.541   01.431
PMU metrics    c1=000024       c2=000054
semaphore      000992/000334   001130/000334    00.984   01.121   01.017  03.068
               Cortex-A7 TRM result             01.000   01.166   01.030
PMU metrics    c1=000016       c2=000040
mutex          001142/000412   001514/000412    01.132   01.501   01.177  02.881
               Cortex-A7 TRM result             01.125   01.541   01.192
PMU metrics    c1=000022       c2=000052

замер таймером:

T133 Smp DRAM =======================================
Perfomance info threadX CPUclk=1008MHz
service_name                                    min[uS]  max[uS]  avg[uS]
queue          Cortex-A7 TRM result             01.041   01.375   01.073
eventFlag      Cortex-A7 TRM result             01.333   01.500   01.385
semaphore      Cortex-A7 TRM result             00.958   01.041   00.976
mutex          Cortex-A7 TRM result             01.125   01.500   01.164

SMP версия threadX, тестовые задачи запущены на разных ядрах, замер возможен только таймером:

T133 Smp DRAM  =======================================
Perfomance info threadX CPUclk=1008MHz
service_name                                    min[uS]  max[uS]  avg[uS]
queue          Cortex-A7 TRM result             00.791   01.041   00.837
eventFlag      Cortex-A7 TRM result             00.625   02.250   01.332
semaphore      Cortex-A7 TRM result             00.500   01.750   00.865
mutex          Cortex-A7 TRM result             00.750   02.083   01.494

Анализируем результаты (smp версия запускалась при чуть иной инициализации памяти DRAM, но на результатах это слабо сказывалось).

Переход на SMP версию rtos сопровождается заметным усложнением служебного кода — число инструкций увеличивается в 2–2.5, кроме того увеличивается CPI (cycles per instruction), что косвенно свидетельствует об увеличении числа ветвлений в коде и, соответственно, увеличении числа промахов предсказателя ветвлений. В этом режиме наблюдается ухудшение времени реакции rtos примерно в 4 раза.

Однако, разнесение задач по разным ядрам уменьшает эти штрафы времени! Вероятно, поскольку код делится на разные ядра. Итоговый результат — ухудшение относительно одно процессорной версии в 1.5–2 раза. Считаю вполне хороший результат.

И небольшое дополнение. Раз уж тестик обновлен, обновил результаты сравнения разных rtos.
Это сравнение запущено на плате с одноядерным Allwinner V3s.

threadX 6.1:

#define TX_DISABLE_ERROR_CHECKING
#define TX_INLINE_THREAD_RESUME_SUSPEND
Perfomance info CPUclk=0900MHz
service_name   min[cyc/inst]   max[cyc/inst]    min[uS]  max[uS]  avg[uS] cpi
queue          000272/000142   000353/000142    00.302   00.392   00.322  02.035
               Cortex-A7 TRM result             00.291   00.375   00.345
PMU metrics    c1=000004       c2=000016
eventFlag      000303/000161   000343/000161    00.336   00.381   00.342  01.913
               Cortex-A7 TRM result             00.333   00.416   00.373
PMU metrics    c1=000003       c2=000017
semaphore      000240/000132   000262/000132    00.266   00.291   00.270  01.840
               Cortex-A7 TRM result             00.291   00.333   00.294
PMU metrics    c1=000002       c2=000014
mutex          000333/000182   000358/000182    00.370   00.397   00.374  01.846
               Cortex-A7 TRM result             00.375   00.416   00.391
PMU metrics    c1=000007       c2=000025

embos 4.38 (os7t_al_r.a):

Perfomance info  CPUclk=0900MHz
service_name   min[cyc/inst]   max[cyc/inst]    min[uS]  max[uS]  avg[uS] cpi
queue          000737/000382   000876/000382    00.818   00.973   00.867  02.044
               Cortex-A7 TRM result             00.833   01.000   00.876
PMU metrics    c1=000012       c2=000046
eventFlag      000580/000278   000673/000278    00.644   00.747   00.659  02.133
               Cortex-A7 TRM result             00.666   00.750   00.683
PMU metrics    c1=000009       c2=000035
semaphore      000533/000273   000591/000273    00.592   00.656   00.597  01.967
               Cortex-A7 TRM result             00.583   00.666   00.617
PMU metrics    c1=000002       c2=000034
mutex          000894/000474   001086/000474    00.993   01.206   01.039  01.972
               Cortex-A7 TRM result             01.000   01.250   01.061
PMU metrics    c1=000014       c2=000063

Здесь мы видим, что качество кода threadX несколько повыше. В нем используется довольно много асм вставок, полагаю, для оптимизации траектории исполнения кода в случае наиболее вероятных ветвлений, что отражено в лучшей цифре CPI.

В общем, результат нынешнего теста согласуется с прогоном аналогичного теста в 2017, когда происходил переход threadx→freertos→embos. Точных цифр под рукой нет, надо искать где‑то тот файлик, но на память такое соотношение (2–3) и было. А freertos тогдашняя (9.0.0) отставала от эти обеих ну просто ОЧЕНЬ сильно. Сейчас заниматься запуском freertos на этих платах совершенно неинтересно.