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