продолжение темы 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);
OS_task_resume(&TEST2Thread);

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 версию 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 на этих платах совершенно неинтересно.

Комментарии (6)


  1. aao_1965 Автор
    27.07.2026 07:45

    не знаю как "убрать под кат" слишком длинные блоки исх текста


  1. Brazil
    27.07.2026 07:45

    Кратко: 4 претензии

    1. Конфиг сборки не указан — цифры не воспроизводимы

    // эти две сборки различаются в разы, а в статье не сказано, какая:
    #define TX_DISABLE_ERROR_CHECKING      // 3 вызова × шелл _txe_* минус
    #define TX_INLINE_THREAD_RESUME_SUSPEND
    #define TX_NOT_INTERRUPTABLE
    // vs дефолт (всё выключено) + TX_ENABLE_EVENT_TRACE

    Плюс грабли: правки в tx_user.h не работают, если библиотека собрана без TX_INCLUDE_USER_DEFINE_FILE.

    2. Меряется не то, что в шапке таблицы

    TEST1_PRIORITY = 15, TEST2 = 16, в ThreadX 0 — старший ⇒ task1 приоритетнее и не вытесняется:

    perf_test_prepare(sid1);       // t1
    OS_queue_send(...);            // task2 ready, НО не вытесняет
    OS_evf_get(&TEST1SrvEvf, ...); // ← вот здесь блокировка + switch
    // task2: fixCnt()             // t2

    Строка queue = 0.564 = queue_send + evf suspend + switch. Одна и та же добавка сидит во всех четырёх строках. Фикс — сделать получателя старше:

    #define TEST1_PRIORITY  (PRIO_LVL 16)   // отправитель
    #define TEST2_PRIORITY  (PRIO_LVL 15)   // получатель — вытесняет сразу

    3. embOS мог быть слинкован в debug-режиме

    #define OS_LIBMODE_R   // release  ← так надо
    #define OS_LIBMODE_DP  // debug+profiling ← дефолт стартпроекта SEGGER

    Отставание ×2–2.5 и +25…110% инструкций — ровно порядок слоя проверки параметров. Вывод «у ThreadX код лучше» недоказан.

    4. Вывод сделан по среднему, а джиттер выброшен

    mutex, uni:        min 0.458  max 0.583   → разброс 0.13 мкс
    mutex, разн. ядра: min 0.750  max 2.083   → разброс 1.33 мкс  (×10)

    Среднее почти вернулось — детерминизм рухнул. Для hard real-time это и есть результат. И 64 прохода на worst case не годятся: нужны десятки тысяч и перцентили, а не min/max.

    Мелочи: для мьютекса кросс-ядерно ×3.2, а не заявленные «1.5–2»; SMP-прогон шёл на DRAM 936 против 792 МГц (штраф SMP скорее занижен).


    1. aao_1965 Автор
      27.07.2026 07:45

      заменю результаты в теле статьи


  1. aao_1965 Автор
    27.07.2026 07:45

    особенно тщательно не анализировал влияние отключения контроля ошибок. на первый взгляд отключение оного дает выигрыш незначительный, до 10% примерно
    в данном тесте в обеих ос контроль ошибок не отключался.
    вот прогон embos в варианте release (os7t_al_r.a)

    Perfomance info threadX CPUclk=0900MHz
    service_name   min[cyc/inst]   max[cyc/inst]    min[uS]  max[uS]  avg[uS] cpi     
    queue          000998/000488   001976/000488    01.108   02.195   01.236  02.278  
                   Cortex-A7 TRM result             01.125   01.916   01.240  
    PMU metrics    c1=000015       c2=000060 
    eventFlag      000796/000372   001030/000372    00.884   01.144   00.967  02.338  
                   Cortex-A7 TRM result             00.875   01.166   00.983  
    PMU metrics    c1=000007       c2=000044 
    semaphore      000615/000332   001255/000332    00.683   01.394   00.696  01.888  
                   Cortex-A7 TRM result             00.708   01.208   00.718  
    PMU metrics    c1=000005       c2=000038 
    mutex          000703/000328   001838/000328    00.781   02.042   00.817  02.240  
                   Cortex-A7 TRM result             00.791   01.833   00.839  
    PMU metrics    c1=000010       c2=000038 

    чуть быстрее, но непринципиально


  1. aao_1965 Автор
    27.07.2026 07:45

    хммм...

    #define TEST1_PRIORITY  (PRIO_LVL 16)   // отправитель
    #define TEST2_PRIORITY  (PRIO_LVL 15)   // получатель — вытесняет сразу

    то есть я не то меряю? а вероятно может быть
    сейчас перемеряю


    1. aao_1965 Автор
      27.07.2026 07:45

      действительно не то, спасибо!
      вот результат когда test1 более приоритетна
      embos **_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
      tx_user.h пустой

      Perfomance info CPUclk=0900MHz
      service_name   min[cyc/inst]   max[cyc/inst]    min[uS]  max[uS]  avg[uS] cpi
      queue          000309/000162   000347/000162    00.343   00.385   00.346  01.919
                     Cortex-A7 TRM result             00.333   00.375   00.371
      PMU metrics    c1=000006       c2=000022
      eventFlag      000344/000174   000419/000174    00.382   00.465   00.396  02.045
                     Cortex-A7 TRM result             00.416   00.500   00.418
      PMU metrics    c1=000005       c2=000021
      semaphore      000259/000139   000292/000139    00.287   00.324   00.298  01.928
                     Cortex-A7 TRM result             00.291   00.375   00.329
      PMU metrics    c1=000005       c2=000016
      mutex          000339/000193   000371/000193    00.376   00.412   00.386  01.803
                     Cortex-A7 TRM result             00.375   00.458   00.413
      PMU metrics    c1=000006       c2=000028

      tx_user.h содержит

      #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