我在统计如下代码耗时的时候,发现有这么个问题。
这段代码是对im和meanparameter两个指针对应数据做加法,在STM32F746上运行。通过DWT-CYCCNT来计算时间。其中两个做加法的指针指向两个随机生成的float数组,运算使用了FPU。对于每一种size,做一万次运算后取平均值,即为该size下的平均耗时
然而随着我增加size的大小,却发现循环耗时不是跟着size增加呈线性变化的。
如图所示,在800之前,拟合的曲线是个二次方函数,甚至R是1.
其中常数项29可以解释,图中的这个曲线是包括做加法的这个函数的压栈出栈和参数传递的时间。再加上branch的时间和CYCCNT置零后的时间,大概就是29左右。
其中一次方系数也可以解释,就是while 这个loop,每个loop的平均耗时。
二次方系数我就不知道是什么了。
进一步的,我把size加到一万之后,zise与耗时关系图长这样
发现一次方系数变大,二次方系数几乎没有了。
到目前为止大概是正常的,但是把一次方项系数单独拿出来说之后,有个奇怪的图像就出来了。
上图是size与一方次系数的关系图,比如在5000的那个点,就是对0~5000的几个点进行拟合得到的一次项的系数。
发现随着size变大,一次方系数由1时候13迅速的在800左右减小到5,然后稳定到7附近。简直是个完美的,滤波过程。pid?
最开始发现这个问题的时候,我以为是数据的问题,比如FPU数据对齐需要花费时间,对齐用的时间不同,计算速度也就不同,但是我用随机生成的数据测试依然有这种情况,说明不是数据的问题。
后来怀疑是fpu的问题,然后我使用随机生成的0~1024的数据来测试,结果是这样的
他的一次方系数是这样的
这就很奇怪了,这说明如果我要算一个加法循环,最有效率的循环次数是二百左右(用总耗时除以循环次数)。无论大了还是小了效率都会变低。尤其是如果循环只有十几次的话,效率是非常低的。
暂时没有测试乘法,因为我现在觉得是branch predictor的问题,但又觉得不像,因为分支预测的准确率应该至少95%才对,不应该出现波动这么大的情况(从十三到五,说明这个一开始的refill都浪费了七八个周期)。如果真的是分支预测的问题,其他核是不是也存在这么一个,最佳循环次数呢?
/
听你的描述,一开始的波动像基本上确定是branch prediction训练需要过程的结果。后来loop变长可能是和内存访问/cache miss有关,但是仅靠帖子里的信息没办法确认。
谢谢回复。只是不明白分支预测的训练要几百个周期才能稳定下来?
对于同一个branch,分支预测训练2次就可以稳定了。
我重新review了你的实验过程,我不建议你用二项式拟合执行时间并分析一次项和二次项趋势,最直接的还是看每个size loop的执行时间。建议你看一下反汇编代码,确定真正的程序流。
我有一个问题,你的横坐标是size(while的变量),那外部是不是还有一个1万次的循环?小size loop执行时间长很可能和这个外部循环开销有关。
谢谢,但是如果直接看每个loop的时间,我担心上一个loop的指令和下个loop的指令会有dual-issue之类的,如果在之间插入测时间的指令会影响结果,
外面是有一个一万次的循环,不太明白为什么会跟这个有关系。
反汇编是这样的,感觉没有什么特别的地方,唯一有影响的大概就是分支跳转了
0x0800241A B510 PUSH {r4,lr} 84: LK_Accuarcy_Data *meanParameter=ZeroCenterParameter->Matrix; 85: 86: while (Size--) 87: { 0x0800241C 690A LDR r2,[r1,#0x10]0x0800241E E9D01003 LDRD r1,r0,[r0,#0x0C]0x08002422 E003 B 0x0800242C 88: *im = *im + *meanParameter; 89: im++; 90: meanParameter++; 91: } 92: 0x08002424 CA10 LDM r2!,{r4}0x08002426 6803 LDR r3,[r0,#0x00]0x08002428 4423 ADD r3,r3,r40x0800242A C008 STM r0!,{r3}0x0800242C 1E49 SUBS r1,r1,#1 86: while (Size--) 87: { 88: *im = *im + *meanParameter; 89: im++; 90: meanParameter++; 91: } 92: 0x0800242E D2F9 BCS 0x08002424 93: } 0x08002430 BD10 POP {r4,pc}
我不是说去测每一个while(size) loop的时间,而是让你看10000次总的时间除以10000之后得到的平均时间。这样就不会影响while(size)循环了。
你没有给出10000次循环的C代码和反汇编,这个循环也有分支和别的指令造成的开销。当循环内程序主体while(size)比较大的时候,这部分开销的影响不明显。但是当while(size)小的时候,外部循环的开销就相对大了。这样的结果就是你看到的 size小的时候时间比预期的要长。
start_time=time
repeat 10000{ // 这个repeat循环也要花时间
While(size--){
....
}
end_time=time
我没有测一万次的总时间,我在这个while(size--)前后布置了DWT,,用来测试wile(size--)的执行时间,外面那个大循环while(一万--)跟这个while(size--)中间还有好多别的程序比如产生随机数据,printf
while(一万--)
{
一堆其他程序
DWT->CYCCNT=0;
while(size--)
num=DWT->CYCCNT;
printf(num-1);
测一万的总时间再除以一万就不如测一万次除以一万来的精确了。不太明白外面的大while怎么影响小while的运行,毕竟每次小while之前我都把计数器清零
而且在其他板子,比如我用M4测得就是很漂亮的线性关系。搞不懂M7的pipeline和dual-issue在干啥
根据你的描述,可以排除外部10000次循环的影响了。
我注意到while(size)循环体里包含了大量的load/store指令,这个应该是造成你看到实验现象的主要因素。
load store指令的执行时间和memory system的状态有关,你确认一下M7访问的内存空间是TCM,Cachable AXIM空间还是non-cacheable AXIM空间。 M7以burst形式AXI访问外部memory,一般会有一个比较大的延时。在loop size为1的时候,这个延时算在一个loop里。loop size为2的时候,这个延时平摊在2个loop里,以此类推到一条cache line长度。
M4是AHB协议,访问memory的延时比较固定,所以这个现象不明显。
要验证这点,你可以提前把整个实验数据都放到cache里再进行实验,应该就能看到预期的结果了。