?????其实已经有很多通过提供一个简单的数来尝试整体评估
C
P
U
CPU
CPU的性能基准,比如主频(
M
H
z
MHz
MHz), 每秒百万指令(Million Instructions per Second,MIPS), 每秒百万操作(Million Operations per Second,MOPS),每秒百万浮点操作(Million Floating Point Operations per Second,MFLOPS)。但是当我们在看CPU的整体的潜在性能的时候,这些基准往往不是那么能够准确的反映。
D
h
r
y
s
t
o
n
e
Dhrystone
Dhrystone基准是第一个尝试将性能指标个实际代码执行联系起来的基准。 ?????目前还是存在着对一种能够提供
C
P
U
CPU
CPU核的有意义的信息的简单和标准的基准的需求。
E
E
M
B
C
EEMBC
EEMBC的
C
o
r
e
M
a
r
k
CoreMark
CoreMark是一个用来评估用于嵌入式系统的微控制器(
M
C
U
MCU
MCU)和中央处理单元(
C
P
U
CPU
CPU)的性能。它取代了过时的
D
h
r
y
s
t
o
n
e
Dhrystone
Dhrystone基准。
C
o
r
e
M
a
r
k
CoreMark
CoreMark将性能指标和简单的,在实际的应用中常用的代码的执行联系起来。比如线性表处理、矩阵操作、状态机和循环冗余校验。
C
o
r
e
M
a
r
k
CoreMark
CoreMark确保测试的代码运行都在运行时进行而防止被编译器优化。 ?????
C
o
r
e
M
a
r
k
CoreMark
CoreMark只是
E
E
M
B
C
EEMBC
EEMBC组织开发的许多评估不同软件和硬件性能的基准中的一个基准,如图1所示。图1来至于
E
E
M
B
C
EEMBC
EEMBC的官网https://www.eembc.org/about/对
E
E
M
B
C
EEMBC
EEMBC的介绍。
图1.
?????好了,现在开始讲一下如何利用
C
o
r
e
M
a
r
k
CoreMark
CoreMark在正点原子的
S
T
M
32
F
103
Z
E
T
6
STM32\quad F103ZET6
STM32F103ZET6精英板上跑分。先去
G
i
t
H
u
b
GitHub
GitHub上https://github.com/eembc/coremark下载
C
o
r
e
M
a
r
k
CoreMark
CoreMark的源码。如图2所示。下载完之后我们只需要图3中红圈中的文件和
s
i
m
p
e
simpe
simpe子目录下的
c
o
r
e
_
p
o
r
t
m
e
.
h
core\_portme.h
core_portme.h,
c
o
r
e
_
p
o
r
t
m
e
.
c
core\_portme.c
core_portme.c。
c
o
r
e
_
p
o
r
t
m
e
.
h
core\_portme.h
core_portme.h和
c
o
r
e
_
p
o
r
t
m
e
.
c
core\_portme.c
core_portme.c也是我们在移植过程中需要修改的文件。其它一些子文件夹是对应其它平台的,如果你是对应的平台的话可以自己去查找对应的资料。
图2.
图3.
图4.
?????现在把前面提到的文件都添加到工程中。如图5所示。这里要改的主要是
c
o
r
e
_
p
o
r
t
m
e
.
h
core\_portme.h
core_portme.h文件和
c
o
r
e
_
p
o
r
t
m
e
.
c
core\_portme.c
core_portme.c文件。
c
o
r
e
_
p
o
r
t
m
e
.
h
core\_portme.h
core_portme.h里面主要是一些宏定义参数,更改如下。其它的保持默认值就好。
宏定义值 | 说明 |
---|
#define ITERATIONS (1000) | 定义整个测试过程中测试程序的迭代次数 | #define HAS_FLOAT 1 | 虽然stm32 f103zet6不支持硬件浮点运算,但是KEIL MDK似乎有软件浮点库 | #define HAS_TIME_H 0 | 虽然有time.h文件,但是对应的时间函数没有实现 | #define USE_CLOCK 0 | 虽然有time.h文件,但是对应的时间函数没有实现 | #define HAS_STDIO 1 | KEIL MDK 平台有stdio.h文件 | #define HAS_PRINTF 1 | 我们已经重定义了相关函数,因此可以直接使用printf函数来进行串口输入输出 | #define MEM_LOCATION “STACK” | 在栈中获取额外的内存空间 | #define MEM_METHOD MEM_STACK | 在栈中获取额外的内存空间 | #define MULTITHREAD 1 | 不支持多进程 | #define MAIN_HAS_NOARGC 1 | 不支持main函数接受参数 | #define MAIN_HAS_NORETURN 0 | main函数有返回值 |
图5.
?????对于
c
o
r
e
_
p
o
r
t
m
e
.
h
core\_portme.h
core_portme.h文件中的宏
C
O
M
P
I
L
E
R
_
V
E
R
S
I
O
N
COMPILER\_VERSION
COMPILER_VERSION和
C
O
M
P
I
L
E
R
_
F
L
A
G
S
COMPILER\_FLAGS
COMPILER_FLAGS,对应的定义为自己的编译器版本和编译的时候使用的参数标志就可以,如图6、7、8所示。其实这两个宏对程序运行的结果没有任何的影响,只是遵循了
C
o
r
e
M
a
r
k
CoreMark
CoreMark结果的标准形式输出。
图6.
图7.
图8.
?????对于
c
o
r
e
_
p
o
r
t
m
e
.
c
core\_portme.c
core_portme.c文件,因为单片机的
t
i
m
e
.
h
time.h
time.h相应的时间函数无法使用,所以要在这里实现可以计时的函数,整个的修改就是围绕实现计时的功能。具体原理是首先设定系统滴答定时器中断的相应周期为1
m
s
ms
ms,即每过1
m
s
ms
ms就会调用一次
S
y
s
T
i
c
k
_
H
a
n
d
l
e
r
SysTick\_Handler
SysTick_Handler,并且设置一个计数索引
t
i
c
k
s
ticks
ticks来记录
S
y
s
T
i
c
k
_
H
a
n
d
l
e
r
SysTick\_Handler
SysTick_Handler被调用的次数,没调用一次
S
y
s
T
i
c
k
_
H
a
n
d
l
e
r
SysTick\_Handler
SysTick_Handler计数索引
t
i
c
k
s
ticks
ticks的值就会被加1。在程序开始和结束的时候分别存储当前的计数索引
t
i
c
k
s
ticks
ticks的值到变量
s
t
a
r
t
_
t
i
m
e
_
v
a
l
start\_time\_val
start_time_val
s
t
o
p
_
t
i
m
e
_
v
a
l
stop\_time\_val
stop_time_val,那么这两个变量的差值就是整个程序运行的时间的毫秒数,再除以1000就是秒数。 ?????对于系统滴答定时器的初始化和关闭的接口我放在
T
I
M
E
TIME
TIME目录下,如图5所示。我这里
H
C
L
K
HCLK
HCLK为72
M
H
Z
MHZ
MHZ,我把系统滴答定时器的初始化和关闭的接口调用放在了
c
o
r
e
_
p
o
r
t
m
e
.
c
core\_portme.c
core_portme.c文件中的
p
o
r
t
a
b
l
e
_
i
n
i
t
portable\_init
portable_init函数和函数
p
o
r
t
a
b
l
e
_
f
i
n
i
portable\_fini
portable_fini中。
#include "coremark_time.h"
u32 ticks=0;
void coremark_time_init(void)
{
u16 ticks_Per_Millisecond=9000;
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
SysTick->LOAD = (ticks_Per_Millisecond & SysTick_LOAD_RELOAD_Msk) - 1;
SysTick->VAL = 0;
SysTick->CTRL = SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk;
return;
}
void coremark_time_d_init(void)
{
SysTick->CTRL &= !(SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk);
SysTick->VAL = 0;
return;
}
void SysTick_Handler(void)
{
ticks++;
}
void portable_init(core_portable *p, int *argc, char *argv[])
{
if (sizeof(ee_ptr_int) != sizeof(ee_u8 *))
{
ee_printf( "ERROR! Please define ee_ptr_int to a type that holds a ""pointer!\n");
}
if (sizeof(ee_u32) != 4)
{
ee_printf("ERROR! Please define ee_u32 to a 32b unsigned type!\n");
}
p->portable_id = 1;
coremark_time_init();
}
void portable_fini(core_portable *p)
{
p->portable_id = 0;
coremark_time_d_init();
}
?????最后还要注意一下
c
o
r
e
m
a
r
k
.
h
coremark.h
coremark.h文件中的宏
T
O
T
A
L
_
D
A
T
A
_
S
I
Z
E
TOTAL\_DATA\_SIZE
TOTAL_DATA_SIZE,如图9所示,它定义了整个
c
o
r
e
m
a
r
k
coremark
coremark测试的内存空间,所以你在
s
t
a
r
t
u
p
_
s
t
m
32
f
10
x
_
h
d
.
s
startup\_stm32f10x\_hd.s
startup_stm32f10x_hd.s中配置的栈的空间最好不要小于2
K
B
KB
KB,否则你在测试的时候可能会进入
H
a
r
d
F
a
u
l
t
_
H
a
n
d
l
e
r
HardFault\_Handler
HardFault_Handler。我这里配置的是4
K
B
KB
KB。同时宏
T
O
T
A
L
_
D
A
T
A
_
S
I
Z
E
TOTAL\_DATA\_SIZE
TOTAL_DATA_SIZE似乎也决定了是性能运行(宏PERFORMANCE_RUN)、验证运行(宏VALIDATION_RUN)或者是概述运行(宏PROFILE_RUN),如图10所示。但是宏
T
O
T
A
L
_
D
A
T
A
_
S
I
Z
E
TOTAL\_DATA\_SIZE
TOTAL_DATA_SIZE默认就定义为2000,因此业绩是性能运行,至于其具体的含义我这里也不是很清楚。因为工程里有我们自己的
m
a
i
n
main
main函数,因此
c
o
r
e
_
m
a
i
n
.
c
core\_main.c
core_main.c文件里面的
m
a
i
n
main
main函数需要改名,什么名字都可以,我这里改为
c
o
r
e
m
a
r
k
_
m
a
i
n
coremark\_main
coremark_main。
图9.
图10.
?????最后我们来看一下测试结果。测试结果如图11所示。两次结果分别对应没有开启优化和开启优化后的结果(优化选项如图12中的红圈中所示,没有优化时,优化
l
e
v
e
l
level
level为0)。这里我设置迭代次数为2000,具体的迭代次数根据自己的需求来设置,但是最少要保证程序能够运行10秒,否则会报错,这是
C
o
r
e
M
a
r
k
CoreMark
CoreMark测试要求的。最后的测试评分就是程序运行的时间除以迭代次数。可以看出优化之后分数明显提高了。我的工程在这里。
图11.
图12.
#include "usart.h"
extern int coremark_main(void);
int main(void)
{
uart_init(USART1,115200);
printf("Coremark test start.\r\n");
coremark_main();
printf("Coremark test end.\r\n\r\n\r\n");
while(1);
return 1;
}
|