C++性能优化系列——3D高斯核卷积计算(九)3D高斯卷积OpenMP Task优化内存访问
本篇基于 C++性能优化系列——3D高斯核卷积计算(八)3D高斯卷积中的代码实现的计算逻辑,通过OepnMP的Task特性,优化Z维度卷积计算中内存性能瓶颈。(本篇只优化内存访问问题,不保证总执行时间会提升)。代码实现void GaussSmoothCPU3DBase_Task(float* pSrc, int iDim[3], float* pKernel, int kernelSize[3],
本篇基于 C++性能优化系列——3D高斯核卷积计算(八)3D高斯卷积中的代码实现的计算逻辑,通过OepnMP的Task特性,优化Z维度卷积计算中内存性能瓶颈。(本篇只优化内存访问问题,不保证总执行时间会提升)。
代码实现
void GaussSmoothCPU3DBase_Task(float* pSrc, int iDim[3], float* pKernel, int kernelSize[3], float* pDst, float* pBuffer)
{
int iSliceSize = iDim[1] * iDim[0];
int nCenter = kernelSize[0] / 2;
#pragma omp parallel num_threads(8)
{
#pragma omp single
for (int z = 0; z < (iDim[2]); z++)
{
#pragma omp taskgroup
{
#pragma omp task
{
float* pSrcSlice = pSrc + z * iSliceSize;
float* pBuffSlice = pBuffer + z * iSliceSize;
memset(pBuffSlice, 0, iSliceSize * sizeof(float));
for (int y = 0; y < iDim[1]; y++)
{
float* pSrcLine = pSrcSlice + y * iDim[0];
float* pDstLine = pBuffSlice + y * iDim[0];
Conv1D_Opt_Cmb(pSrcLine, iDim[0], pKernel, kernelSize[0], pDstLine);
}
for (int y = 0; y < (iDim[1] - kernelSize[0] + 1); y++)
{
float* pDstLine = pSrcSlice + (y + nCenter) * iDim[0];
memset(pDstLine, 0, iDim[0] * sizeof(float));
for (int kx = 0; kx < kernelSize[0]; kx++)
{
float* pSrcLine = pBuffSlice + (y + kx) * iDim[0];
//ippsAddProductC_32f(pSrcLine, pKernel[kx], pDstLine, iDim[0]);
#pragma omp simd aligned(pSrcLine, pDstLine)
for (int i = 0; i < iDim[0]; i++)
{
pDstLine[i] += pSrcLine[i] * pKernel[kx];
}
}
}
}
}
}
#pragma omp single
for (int z = 0; z < (iDim[2] - kernelSize[0] + 1); z++)
{
#pragma omp taskgroup
{
#pragma omp task
{
float* pDstSlice = pDst + (z + nCenter) * iSliceSize;
memset(pDstSlice, 0, iSliceSize * sizeof(float));
for (int kx = 0; kx < kernelSize[0]; kx++)
{
float* pSrcSlice = pSrc + (z + kx) * iSliceSize;
#pragma omp simd
for (int i = 0; i < iSliceSize; ++i)
{
pDstSlice[i] += pKernel[kx] * pSrcSlice[i];
}
}
}
}
}
}
}
执行时间
GaussSmoothCPU3DBase_Task cost Time(ms) 1269.8
可以看到执行时间上下降很多。
VTune分析性能

可以看到计算本身的耗时减少了,但是使用OpenMP的Task特性导致线程调度的耗时增加了。

但是线程一直在执行有效的内容。
查看计算部分的总体执行情况,可以看到之前的内存访问问题得到改善。
查看热点语句,主要的热点已经由Z维度的计算改变为计算部分的for语句。
分析:这里使用了Task特性,Task执行过程:每个线程执行一次迭代,执行结束再获取一个迭代任务。OpenMP for执行过程:每个线程一次性获取432 /8 个迭代。因此,并行区内不同的线程处理的数据在L3 Cache上得到了一定的复用。
为了验证这一点,在计算部分添加输出打印出计算的Z值ID 和线程ID。
z = 0omp tid = 0
z = 1omp tid = 0
z = 2omp tid = 5
z = 3omp tid = 0
z = 4omp tid = 6
z = 5omp tid = 0
z = 6omp tid = 1
z = 7omp tid = 1
z = 8omp tid = 0
z = 9omp tid = 6
z = 10omp tid = 0
z = 11omp tid = 7
z = 12omp tid = 3
z = 13omp tid = 5
z = 14omp tid = 0
z = 15omp tid = 0
z = 16omp tid = 0
z = 17omp tid = 0
z = 18omp tid = 7
z = 19omp tid = 0
z = 20omp tid = 0
z = 21omp tid = 2
z = 22omp tid = 0
z = 23omp tid = 5
z = 24omp tid = 6
z = 25omp tid = 6
z = 26omp tid = 5
z = 27omp tid = 0
z = 28omp tid = 1
z = 29omp tid = 6
z = 30omp tid = 0
这里只记录前30个Z值映射的线程,可以看到并不是同一个线程处理这部分内容。
总结
本篇通过OpemMP Task 特性,对优化了内存访问问题。但是由于Task的调度方式本身会有额外的消耗,导致总时间变慢。但是对优化内存访问问题依然有借鉴意义。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐




所有评论(0)