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

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 скорее занижен).

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чуть быстрее, но непринципиально

aao_1965 Автор
27.07.2026 07:45хммм...
#define TEST1_PRIORITY (PRIO_LVL 16) // отправитель #define TEST2_PRIORITY (PRIO_LVL 15) // получатель — вытесняет сразуто есть я не то меряю? а вероятно может быть
сейчас перемеряю
aao_1965 Автор
27.07.2026 07:45действительно не то, спасибо!
вот результат когда test1 более приоритетна
embos **_r.aPerfomance 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=000028tx_user.h содержит
#define TX_DISABLE_ERROR_CHECKING #define TX_INLINE_THREAD_RESUME_SUSPENDPerfomance 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
aao_1965 Автор
не знаю как "убрать под кат" слишком длинные блоки исх текста