大家好,
我最近尝试把CPU上跑的程序移植到DSP上。听说代码在DSP上需要进行一定程度的优化才能赶上CPU的性能,所以这段时间我对DSP的优化进行了学习。
我尝试在DSP上测试一些基本的程序,然后查看它的测试结果,但是这其中发现很多我无法解释的问题。首先,我解释介绍一下我的环境:
我用的是TI C6678的EVM开发板,我跑的是SYS/BIOS,我在Core0上建了一个Task,在这个task上我写了一个基本的三重循环的程序:
for (i = 0; i < NI; i++)
{
for (j = 0; j < NJ; j++)
{
C[i*NJ + j] *= BETA;
for (k = 0; k < NK; ++k)
{
C[i*NJ + j] += ALPHA * A[i*NK + k] * B[k*NJ + j];
}
}
}
其中NI,NL,NK都是4096.
在运行程序的时候,我发现了两件事情:
1. 当我使用restrict来限定A和B的时候,software pipeline会对程序做一定的优化,理想的运行时间会快三倍左右。但是系统能做出这么大的优化不是很可信。于是,我比较使用和不适用restrict关键词之后输出结果C[ ],然后我发现他们的计算结果存在一些一定的误差。restrict关键词是用在没有bad alias的时候,在程序中A,B,C之间没有数据重叠的情况,我不是很确定为什么restrict会对计算结果产生影响。
2. 我尝试打印一些运行时间,
// // Get Stop Time
// g_ui64StopTime = (uint64_t)(TSCL) ;
// g_ui64StopTime |= (uint64_t)((uint64_t)TSCH << 32 ) ;
// g_ui64ElapsedTime = g_ui64StopTime – g_ui64StartTime;
//
// // Get Start Time
// g_ui64StartTime = (uint64_t)(TSCL) ;
// g_ui64StartTime |= (uint64_t)((uint64_t)TSCH << 32 ) ;
然后发现编译之后software pipeline又出现了不同的优化结果。当我打印每一个k循环的时候,software pipeline显示的理想运行时间又提高了三倍,但是我在实际运行的时候,整体的运行时间却增长了很多。
另一件奇怪的事情是,当我打印每一个J循环的时候,software pipeline并没有什么不同的优化结果,运行的时间约为25分钟,但是如果我不打印任何时间的时候,整个运行的时间是140分钟 (我用手表掐表的)。
这两个问题至今让我摸不着头脑,如果大家有什么经验分享给我,我不胜感激。
Adam Yao94020:
首先你要确定一下你定义的三个数组的长度,在循环中是否存在Index超出最大有效范围的可能性。
如果三个数组不存在重叠,使用restirct可以最大限度给编译器自由去优化循环,三倍的增益也是有可能的。如果循环次数固定的话,建议使用#pragma MUST_ITERATE通知编译器。运行结果不一致是不正常的,你需要仔细检查程序的合理性,
在循环内部加入打印,会导致编译器无法进行软件流水,不建议这么做。
Jie Zhang18:
回复 Adam Yao94020:
Hi Adam,
谢谢你的回复。
1. 我在DRAM里面定义了三个等长的数组,
#pragma DATA_SECTION(pfBuffer_A, ".ddr3_arr")DATA_TYPE pfBuffer_A[(NI+1)*(NJ+1)];
#pragma DATA_SECTION(pfBuffer_B, ".ddr3_arr")DATA_TYPE pfBuffer_B[(NI+1)*(NJ+1)];
#pragma DATA_SECTION(pfBuffer_C, ".ddr3_arr")DATA_TYPE pfBuffer_C[(NI+1)*(NJ+1)];
但是我只用到了NI*NJ的大小,同时我觉得这三个数组不存在重叠的情况。
2. 我也使用MUST_ITERATE,但是基本没有什么效果。
3. 我现在遇到的情况是我在循环里面加入了计算timing的代码之后,执行时间从100分钟减少到25分钟。这个让我百思不得其解。
这个function是这样的:
void GEMM_Test(const DATA_TYPE * restrict A, const DATA_TYPE * restrict B, DATA_TYPE * restrict C, int start_x){ int i,j,k; start_x = start_x * NI; _nassert((int) A % 8 == 0); _nassert((int) B % 8 == 0); _nassert((int) C % 8 == 0);
// Get Start Time g_ui64StartTime = (uint64_t)(TSCL) ; g_ui64StartTime |= (uint64_t)((uint64_t)TSCH << 32 ) ;
#pragma MUST_ITERATE(8,,8)#pragma UNROLL(8)
for (i = 0; i < NI; i++) {
if(i%128==0){ // Get Stop Time g_ui64StopTime = (uint64_t)(TSCL) ; g_ui64StopTime |= (uint64_t)((uint64_t)TSCH << 32 ) ; g_ui64ElapsedTime = g_ui64StopTime – g_ui64StartTime;
// Get Start Time g_ui64StartTime = (uint64_t)(TSCL) ; g_ui64StartTime |= (uint64_t)((uint64_t)TSCH << 32 ) ; //printf("GEMM single-precision execution time is \t%llu\n",g_ui64ElapsedTime); }
for (j = 0; j < NJ; j++) { C[i*NJ + j] *= BETA;
for (k = 0; k < NK; ++k) { C[i*NJ + j] += ALPHA * A[i*NK + k] * B[k*NJ + j]; //C[i*NJ + j] += ALPHA * (i*NK + k)*B[k*NJ + j];
} } }}
Adam Yao94020:
回复 Jie Zhang18:
两种情况下的计算结果你比较过么?是否计算结果跟你预想的一致? 你可以保留编译时产生的汇编文件,并保留软件流水信息(编译选项加-k, -mw),通过汇编代码分别分析一下循环的ii是多少,来比较两种不同写法循环的优化效果。
另外你编译选项选择的是什么?
Jie Zhang18:
回复 Adam Yao94020:
Hi Adam,
两种情况的计算结果我比较过,是一致的。 我保留的软件流水的信息并没有变化,因为我只是在第二重循环里面增加的读取time的代码。
我的编译选项如下:
-mv6600 –abi=eabi -O3 –include_path="C:/ti/ccsv5/tools/compiler/c6000_7.4.4/include" –display_error_number –diag_warning=225 –no_bad_aliases –debug_software_pipeline –entry_parm=address –exit_hook=exit_hook –exit_parm=address –entry_hook=entry_hook
–no_bad_aliases 是 -wt
–debug_software_pipeline 是-wm
–entry_parm=address –exit_hook=exit_hook –exit_parm=address –entry_hook=entry_hook 这个是function hook,我想看DSP profile的信息,网上找到的资料说是用function hook。
如果我哪里有做错的地方,请指正。