一、前言
benchmark 即基准测试。通常操作系统主要服务于应用程序,其运行也是需要一定cpu资源的,一般来说操作系统提供服务一定要快,否则会影响应用程序的运行效率,尤其是实时操作系统。所以本文针对操作系统来做一些基准测试,看看在低端x86平台上,xenomai提供我们平时常用的服务所需要的时间,清楚地了解该平台上一些xenomai服务的消耗,有时能有利于我们进一步优化程序。
目前大多商业实时操作系统会提供详细benchmark测试,比如VxWorks,目前xenomai没有这类的方式,所以借鉴VxWorks的测试方式,对xenomai进行同样测试,所以文章中的测试项命名可能在Linux开发人员看来有点别扭,切勿见怪,其中一些具体流程可见本博客另外一篇文章xenomai与VxWorks实时性对比(资源抢占上下文切换对比)。
测试环境:
CPU:Intel j1900
内存:4GB DDR3
注:测试数据仅供个人参考,单位us,每项测试次数500万次,编写测试用例使用的接口为Alchemy API,原因主要是Alchemy API比较好编写。
二、 测试数据处理
对于每个基准测试,通过在操作前读取时间戳
t
1
t1
t1,该操作完成后读取时间戳
t
2
t2
t2,
t
2
t2
t2与
t
1
t1
t1之间的差值就是测该操作的耗时。
1.1 测试注意事项
需要注意的是,由于我们是基准测试,所以
t
1
t1
t1~
t
2
t2
t2这段时间尽量不要被不相关的事务打断,比如处理不相关的中断、非测试范围内的任务抢占等。为此需要考虑如下。
① 执行测试操作的任务优先级必须最高,两个任务间交互的测试类似。
② 必须检测t1-t2之间的非相关中断,并丢弃对应的测试数据,由于我们已将非xenomai的中断隔离到其cpu0,且无其他实时设备中断,除各种异常外,剩下与xenomai相关的就是定时器中断了,所以仅对tick中断处理,如果测试过程中产生了定时器中断,则忽略这组数据,因此需要为xenomai添加一个系统调用来获取中断信息,测试前后通过该系统调用获中断信息,以此判断测试的过程中有没有中断产生。
③ 读取时间戳的操作也是需要执行时间的,所以需要从结果中减去该时间的影响,测量读取时间戳的需要的时间很简单,通过连续两次读取时间戳
a
1
a1
a1,
a
2
a2
a2,
a
2
−
a
1
a2-a1
a2−a1就是函数 _M_TIMESTAMP()
的执行需要时间。
1.2 数据的处理
得到无误的操作耗时、测试次数后计算平均值最大值、最小值即可;
1.3 测试结构
根据以上,每个测试的流程及代码结构如下:
① 读取起始tick
② 开始测试循环
③ 读取时间戳a
④ 读取起始时间戳b
⑤ 被测试的操作
⑥读取结束时间戳c
⑦判断是否是loadrun,是则丢弃本次结果跳转到③
⑧读取tick,判断本次测试是否位于同一tick内,否则丢弃本次结果跳转到③
⑨判读耗时是都正确(a-b且b-c为正值),是则为有效值,否则丢弃本次结果跳转到③
unsigned long a;
unsigned long b;
unsigned long c;
ULONG tick;
BOOL loadRun = TRUE; /*排除cache对测试的影响,丢弃第一次测试的数据*/
tick = tickGet(); /*确保测试在同一个tick内完成*/
/*循环测试iterations次操作并统计结果*/
for (counter = 0; counter < pData->iterations; counter++)
{
a = _M_TIMESTAMP();
b = _M_TIMESTAMP(); /*起始时间*/
wd = wdCreate ();/*测试的操作*/
c = _M_TIMESTAMP(); /*结束时间*/
/*数据统计处理*/
BM_DATA_RECORD (((c >= b) && (b >= a)), c - b, b - a,
counter, tick, loadRun);
}
二、测试项
明白数据统计处理后剩下的就是其中测试的具体操作了,benchmark 分别对二值信号量(semB)、计数信号量(semC)、互斥量(semM)、读写信号量(SemRW)、任务(Task)、消息队列(msgq)、事件(event)、 中断响应(interrupt)、上下文切换(contexswitch)、时钟抖动(TaskJitter、IntJitter)在各种可能的情况下,测试该操作的耗时。
2.1 时间戳
测试读时间戳耗时bmTimestampRead
。
unsigned long a;
unsigned long b;
ULONG tick;
BOOL loadRun = TRUE; \
tick = tickGet();
for (counter = 0; counter < pData->iterations; counter++)
{
a = _M_TIMESTAMP();
b = _M_TIMESTAMP();
/* validate and record data */
BM_DATA_RECORD ((b > a), b - a, 0, counter, tick, loadRun);
}
min(us) |
avg(us) |
max(us) |
0.084 |
0.094 |
0.132 |
2.2 任务切换
2.2.1信号量响应上下文切换时间
bmCtxSempend: 同一cpu上,高优先级任务对空信号量P操作阻塞,到低优先任务激活的时间。
bmCtxSemUnpend: 同一cpu上,低优先级任务对信号量V操作到高优先任务激活的时间。
CtxSmpAffinitySemUnPend: 高低优先级任务运行于不同cpu上,高优先级任务对空信号量P操作阻塞,到低优先任务激活的时间。
CtxSmpNoAffinitySemUnPend: 不设置亲和性,随系统调度,低优先级任务对信号量V操作到高优先任务激活的时间。
|
min(us) |
avg(us) |
max(us) |
bmCtxSempend |
2.136 |
2.193 |
2.641 |
bmCtxSemUnpend |
2.351 |
2.395 |
2.977 |
CtxSmpAffinitySemUnPend |
0.000 |
0.752 |
2.642 |
CtxSmpNoAffinitySemUnPend |
2.389 |
2.454 |
2.797 |
2.2.2消息队列响应上下文切换时间
bmCtxMsgqPend:同一cpu上,高优先级任务对空消息队列接收数据阻塞,到低优先任务激活的时间。
bmCtxMsgqUnpend:同一cpu上, 低优先级任务写消息队列到高优先任务激活的时间。
CtxSmpAffinityMsgQUnPend:高低优先级任务运行于不同cpu上,高优先级任务对空消息队列接收数据阻塞,到低优先任务激活的时间。
CtxSmpNoAffinityMsgQUnPend:不设置亲和性,随系统调度, 低优先级任务写消息队列到高优先任务激活的时间。
|
min(us) |
avg(us) |
max(us) |
bmCtxMsgqPend |
2.496 |
2.529 |
2.833 |
bmCtxMsgqUnpend |
2.882 |
2.949 |
3.374 |
CtxSmpAffinityMsgQUnPend |
5.245 |
5.497 |
10.589 |
CtxSmpNoAffinityMsgQUnPend |
2.941 |
2.995 |
3.636 |
2.2.3事件响应上下文切换时间
bmCtxMsgqPend:高优先级任务接收事件阻塞,到低优先任务激活的时间。
bmCtxMsgqUnpend: 低优先级任务发送事件到高优先任务激活的时间。
|
min(us) |
avg(us) |
max(us) |
bmCtxEventPend |
- |
- |
- |
bmCtxEventUnpend |
- |
- |
- |
CtxSmpAffinityEventQUnPend |
- |
- |
- |
CtxSmpNoAffinityEventUnPend |
- |
- |
- |
2.2.2.4任务上下文切换时间
bmCtxTaskSwitch:同一cpu上,优先级调度下的任务切换时间。
|
min(us) |
avg(us) |
max(us) |
bmCtxTaskSwitch |
0.703 |
1.633 |
2.594 |
2.3 信号量(Semaphore)
1. 信号量的创建与删除
bmSemBCreate: 创建一个信号量耗时。
bmSemBDelete: 删除一个信号量耗时。
|
min(us) |
avg(us) |
max(us) |
bmSemCreate |
10.433 |
11.417 |
12.977 |
bmSemDelete |
10.276 |
11.431 |
12.317 |
2. 信号量PV操作
SemGiveNoTask:当没有任务阻塞在信号量上时,对空信号量V操作消耗的时间。
SemGiveTaskInQ:同一CPU上,高优先级任务阻塞在信号量时,低优先级任务释放信号量操作消耗的时间。
SemTakeUnavail:单任务对不可用的信号量P操作消耗的时间。
SemTakeAvail:单任务对可用信号量非阻塞P操作消耗的时间。
bmSemGiveTake:单任务对同一信号量连续一次PV操作消耗的时间。
|
min(us) |
avg(us) |
max(us) |
SemGiveNoTask |
0.099 |
0.110 |
0.132 |
SemGiveTaskInQ |
1.837 |
2.036 |
2.281 |
SemTakeAvail |
0.084 |
0.094 |
0.108 |
SemTakeUnavail |
0.111 |
0.125 |
0.144 |
SemGiveTake |
0.187 |
0.192 |
0.198 |
SemPrioInv |
6.531 |
6.842 |
11.968 |
2.4 互斥量(Mutex)
2.4.1 互斥量的创建与删除
MutexCreate:创建一个互斥量耗时。
MutexDelete:删除一个互斥量耗时。
2.4.2 互斥量PV操作
MutexGiveNoTask:当没有任务阻塞在mutex上时,释放mutex操作消耗的时间。
MutexGiveTaskInQ:同一CPU上,高优先级任务阻塞在mutex时,低优先级任务释放mutex操作消耗的时间。
MutexTakeUnavail:当没有mutex可用时,对mutex请求操作的耗时。
MutexTakeAvail:在mutex可用时,请求mutex消耗的时间。
MutexGiveTake:单任务对mutex连续请求释放消耗的时间。
|
min(us) |
avg(us) |
max(us) |
MutexCreate |
2.881 |
2.947 |
3.205 |
MutexDelete |
2.039 |
2.084 |
2.209 |
MutexGiveNoTask |
0.033 |
0.044 |
0.066 |
MutexGiveTaskInQ |
0.047 |
0.117 |
0.228 |
MutexTakeAvail |
0.084 |
0.094 |
0.114 |
MutexGiveTake |
0.118 |
0.122 |
0.148 |
2.5 消息队列(Message Queue)
2.5.1 创建与删除
MsgQCreate:创建一个MsgQ需要的时间。
MsgQDelete:删除一个MsgQ需要的时间。
2.5.2 数据收发
MsgQRecvAvail:当MsgQ内有数据时,接收1Byte数据需要的时间。
MsgQRecvNoAvail:当MsgQ没有数据时,非阻塞接收1Byte数据需要的时间。
MsgQSendPend:高优先级等待数据时,发送1Byte数据需要的时间。
MsgQSendNoPend:没有任务等待数据时,发送1Byte数据需要的时间。
MsgQSendQFull:当MsgQ满时,非阻塞发送1Byte数据需要的时间。
|
min(us) |
avg(us) |
max(us) |
MsgQCreate |
5.991 |
6.324 |
6.855 |
MsgQDelete |
3.733 |
3.849 |
4.046 |
MsgQRecvAvail |
0.240 |
0.279 |
0.396 |
MsgQRecvNoAvail |
0.216 |
0.267 |
0.349 |
MsgQSendPend |
2.401 |
2.647 |
3.902 |
MsgQSendNoPend |
1.223 |
1.262 |
1.536 |
MsgQSendQFull |
0.228 |
0.275 |
0.408 |
2.6 定时器(Alarm)
AlarmCreate:创建一个alarm的时间。
AlarmDelStarted:删除一个已经激活的alarm的时间。
AlarmDelNotStarted:删除一个未激活alarm的时间。
AlarmStartQEmpty:任务没有alarm时,start一个alarm需要的时间。
AlarmStartQEmpty:任务在已有一个 alarm的基础上,再start一个alarm需要的时间。
AlarmCancel:stop一个alarm需要的时间。
|
min(us) |
avg(us) |
max(us) |
AlarmCreate |
4.790 |
4.937 |
7.719 |
AlarmDelStarted |
3.637 |
3.804 |
4.250 |
AlarmDelNotStarted |
3.420 |
3.523 |
4.381 |
AlarmStartQEmpty |
1.860 |
2.079 |
3.158 |
AlarmStartQFull |
1.835 |
1.897 |
2.101 |
AlarmCancel |
1.596 |
1.680 |
2.677 |
2.7 事件(Event)
EventSendSelf: 任务向自己发送一个Event需要的时间。
EventReceiveAvailable: 接收一个已产生的Event需要的时间。
EventReceiveUnavailable: 非阻塞接收一个未产生的Event需要的时间。
EventTaskSendWanted: 高优先级等待Event时,发送Event需要的时间。
EventTaskSendUnwanted: 无任务等待Event时,发送Event需要的时间。
|
min(us) |
avg(us) |
max(us) |
EventSendSelf |
4.790 |
4.937 |
7.719 |
EventReceiveAvailable |
3.637 |
3.804 |
4.250 |
EventReceiveUnavailable |
3.420 |
3.523 |
4.381 |
EventTaskSendWanted |
1.860 |
2.079 |
3.158 |
EventTaskSendUnwanted |
1.835 |
1.897 |
2.101 |
2.8 任务(Task)
2.8.1 任务创建激活
TaskSpawn: 创建并激活一个任务需要的时间。
TaskDelete:删除一个任务需要的时间。
TaskInit:创建一个任务需要的时间。
TaskActivate:激活新创建的任务需要的时间。
2.8.2 任务调度控制
TaskSuspendReady:对一个已经处于ready状态的任务suspend操作需要的时间。
TaskSuspendPend:对一个等待资源处于pend状态的任务进行suspend操作需要的时间。
TaskSuspendSusp:对刚创建的处于Suspend任务 执行Suspend操作需要的时间。
TaskSuspendDelay:对一个处于sleep任务进行suspend操作需要的时间。
TaskResumeReady:对一个处于Ready状态的任务进行Resume操作需要的时间。
TaskResumePend:对一个等待资源处于pend状态的任务进行Resume操作需要的时间。
TaskResumeSusp:对一个处于Suspend状态的任务进行Resume操作需要的时间。
TaskResumeDelay:对一个处于sleep任务进行Resume操作需要的时间。
TaskPrioritySetReady:对一个处于Ready状态任务修改优先级操作需要的时间。
TaskPrioritySetPend:对一个处于pend状态任务修改优先级操作需要的时间。
bmTaskCpuAffinityGet:获取任务的亲和性需要的时间。
bmTaskCpuAffinitySet:设置任务的亲和性需要的时间。
|
min(us) |
avg(us) |
max(us) |
TaskSpawn(1000万次) |
150.649 |
153.859 |
1162.041 |
TaskDelete(1000万次) |
136.074 |
145.766 |
189.952 |
TaskInit(1000万次) |
178.703 |
185.015 |
436.639 |
TaskActivate |
1.052 |
1.336 |
2.986 |
TaskSuspendReady |
1.404 |
1.444 |
1.681 |
TaskSuspendPend |
0.035 |
1.392 |
1.561 |
TaskSuspendSusp |
0.151 |
0.155 |
0.321 |
TaskSuspendDelay |
1.356 |
1.401 |
1.525 |
TaskResumeReady |
0.146 |
0.155 |
0.487 |
TaskResumePend |
0.756 |
0.802 |
0.877 |
TaskResumeSusp |
0.204 |
0.248 |
0.324 |
TaskResumeDelay |
0.180 |
0.228 |
0.300 |
TaskPrioritySetReady |
18.925 |
21.002 |
21.855 |
TaskPrioritySetPend |
19.046 |
21.014 |
28.296 |
TaskCpuAffinityGet |
- |
- |
- |
TaskCpuAffinitySet |
8.332 |
9.541 |
19.808 |
Cyclic:如下操作的流程循环一次的耗时,图中M表示mutex,B表示Semaphore。
/*
Higher Priority Lower Priority
Task1 Task2
=============== ==============
semTake(M)
semGive(M)
|
V
semGive(B)
semTake(B)
|
V
semTake(B)
\
\
\-------------> semTake(M)
semGive(B)
/
/
semTake(M) <-------------/
\
\
\-------------> semGive(M)
/
/
semGive(M) <-------------/
|
V
taskSuspend() <-------------/
\
\
\-------------> taskResume()
/
/
msgQSend() <-------------/
msgQReceive()
|
V
msgQReceive()
\
\
\-------------> msgQSend()
/
/
taskDelay(0) <-------------/
|
V
eventReceive()
\
\
\-------------> eventSend()
/
/
repeat... <-------------/
*/
|
min(us) |
avg(us) |
max(us) |
Cyclic |
33.589 |
34.409 |
36.471 |