本篇基于 C++性能优化系列——3D高斯核卷积计算(六)交换维度计算2D卷积 中2D高斯卷积的计算逻辑,通过Intrinsic函数实现相同的功能并对比性能差异。
复用一维卷积
复用之前实现的函数,用指令实现对x维度的计算
void Conv2D_Fuse_InstructX(float* pSrcSlice, int iDim[2], float* pKernel, int iKernelSize, float* pBuffer, float* pDstSlice,float* pSimdKernel)
{
//combination
int iHalfKernel = iKernelSize / 2;
for (int y = 0; y < (iDim[1] - iKernelSize + 1); y++)
{
float* pDstLine = pDstSlice + (y + iHalfKernel) * iDim[0];
float* pTmpLine = pBuffer + (y + iHalfKernel) * iDim[0];
for (int kx = 0; kx < iKernelSize; ++kx)
{
float* pSrcLine = pSrcSlice + (y + kx) * iDim[0];
#pragma omp simd
for (int i = 0; i < iDim[0]; i++)
{
pTmpLine[i] += pSrcLine[i] * pKernel[kx];
}
}
Conv1D_Ins_Cmb_Unroll2(pTmpLine, iDim[0], pKernel, iKernelSize, pDstLine, pSimdKernel);
}
}
说明:函数Conv1D_Ins_Cmb_Unroll2通过Intrinsic实现一维卷积功能,具体细节参考 C++性能优化系列——3D高斯核卷积计算(四)Intrinsic实现一维卷积与循环展开
执行时间
TestConv2D(Conv2D_Fuse_InstructX) cost total Time(ms) 2817
TestConv2D cost Time(ms) 0.5634
可以看到函数调用耗时与上一版基本持平。
y维度Intrinsic实现
函数实现
void Conv2D_Fuse_InstructYX(float* pSrcSlice, int iDim[2], float* pKernel, int iKernelSize, float* pBuffer, float* pDstSlice, float* pSimdKernel)
{
//combination
int iHalfKernel = iKernelSize / 2;
for (int y = 0; y < (iDim[1] - iKernelSize + 1); y++)
{
float* pDstLine = pDstSlice + (y + iHalfKernel) * iDim[0];
float* pTmpLine = pBuffer + (y + iHalfKernel) * iDim[0];
for (int kx = 0; kx < iKernelSize; ++kx)
{
float* pSrcLine = pSrcSlice + (y + kx) * iDim[0];
__m256 ker = _mm256_loadu_ps(pSimdKernel + 8 * kx);
for (int i = 0; i < iDim[0]; i += 16)
{
__m256 src1 = _mm256_loadu_ps(pSrcLine + i);
__m256 dst1 = _mm256_loadu_ps(pTmpLine + i);
dst1 = _mm256_fmadd_ps(ker, src1, dst1);
_mm256_storeu_ps(pTmpLine + i, dst1);
__m256 src2 = _mm256_loadu_ps(pSrcLine + i + 8);
__m256 dst2 = _mm256_loadu_ps(pTmpLine + i + 8);
dst2 = _mm256_fmadd_ps(ker, src2, dst2);
_mm256_storeu_ps(pTmpLine + i + 8, dst2);
}
}
Conv1D_Ins_Cmb_Unroll2(pTmpLine, iDim[0], pKernel, iKernelSize, pDstLine, pSimdKernel);
}
}
根据之前的经验,通过调用Intrinsic函数手动向量化,for语句对应汇编指令执行过多。这里对y维度的计算进行了手动2倍循环展开。同时,因为x维度的大小为432,是8的整数倍,这里为了追求更优的性能,忽略的拓展性,不实现标量计算部分。
执行时间
TestConv2D(Conv2D_Fuse_InstructYX) cost total Time(ms) 2783
TestConv2D cost Time(ms) 0.5566
VTune性能分析
没有提示整体执行情况中的突出热点
指令执行数量
可以看到总的执行指令与ICC向量化版本基本相同。CPI也相差不大。
执行指令最多的地方依然是计算X维度的卷积计算。可以看到完全用指令实现的版本,for语句执行次数依然超过了内部计算部分指令总的执行次数。这里已经对for进行了2倍展开,解释不了两个版本还会存在这么大的差别的原因。
对应汇编执行情况
反汇编中可以看到add 0x40执行了最多的次数,能够和for语句的内部逻辑对应上。说明VTune统计的确实是for对应执行指令数量。
总结
通过Intrinsic函数做向量化时,由于对应for语句的相对低效执行,使最终执行速度和完全由编译器向量化版本的差别不大。