本篇基于 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的调度方式本身会有额外的消耗,导致总时间变慢。但是对优化内存访问问题依然有借鉴意义。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐