2

我有以下查找和插值代码要优化。(大小为 128 的浮点表)它将与 Windows 上的 Intel 编译器、OSX 上的 GCC 和 neon OSX 上的 GCC 一起使用。

for(unsigned int i = 0 ; i < 4 ; i++)
{
    const int iIdx = (int)m_fIndex[i];
    const float frac = m_fIndex - iIdx;
    m_fResult[i] = sftable[iIdx].val + sftable[iIdx].val2 * frac;
}

我用 sse/neon 对所有东西进行了 vecorized。(宏转换为 sse/neon 指令)

VEC_INT iIdx = VEC_FLOAT2INT(m_fIndex);
VEC_FLOAT frac = VEC_SUB(m_fIndex ,VEC_INT2FLOAT(iIdx);
m_fResult[0] = sftable[iIdx[0]].val2;
m_fResult[1] = sftable[iIdx[1]].val2;
m_fResult[2] = sftable[iIdx[2]].val2;
m_fResult[3] = sftable[iIdx[3]].val2;
m_fResult=VEC_MUL( m_fResult,frac);
frac[0] = sftable[iIdx[0]].val1;
frac[1] = sftable[iIdx[1]].val1;
frac[2] = sftable[iIdx[2]].val1;
frac[3] = sftable[iIdx[3]].val1;
m_fResult=VEC_ADD( m_fResult,frac);

我认为表访问和移动到对齐的内存是这里真正的瓶颈。我不擅长汇编,但有很多 unpcklps 和 mov:

10026751  mov         eax,dword ptr [esp+4270h] 
10026758  movaps      xmm3,xmmword ptr [eax+16640h] 
1002675F  cvttps2dq   xmm5,xmm3 
10026763  cvtdq2ps    xmm4,xmm5 
10026766  movd        edx,xmm5 
1002676A  movdqa      xmm6,xmm5 
1002676E  movdqa      xmm1,xmm5 
10026772  psrldq      xmm6,4 
10026777  movdqa      xmm2,xmm5 
1002677B  movd        ebx,xmm6 
1002677F  subps       xmm3,xmm4 
10026782  psrldq      xmm1,8 
10026787  movd        edi,xmm1 
1002678B  psrldq      xmm2,0Ch 
10026790  movdqa      xmmword ptr [esp+4F40h],xmm5 
10026799  mov         ecx,dword ptr [eax+edx*8+10CF4h] 
100267A0  movss       xmm0,dword ptr [eax+edx*8+10CF4h] 
100267A9  mov         dword ptr [eax+166B0h],ecx 
100267AF  movd        ecx,xmm2 
100267B3  mov         esi,dword ptr [eax+ebx*8+10CF4h] 
100267BA  movss       xmm4,dword ptr [eax+ebx*8+10CF4h] 
100267C3  mov         dword ptr [eax+166B4h],esi 
100267C9  mov         edx,dword ptr [eax+edi*8+10CF4h] 
100267D0  movss       xmm7,dword ptr [eax+edi*8+10CF4h] 
100267D9  mov         dword ptr [eax+166B8h],edx 
100267DF  movss       xmm1,dword ptr [eax+ecx*8+10CF4h] 
100267E8  unpcklps    xmm0,xmm7 
100267EB  unpcklps    xmm4,xmm1 
100267EE  unpcklps    xmm0,xmm4 
100267F1  mulps       xmm0,xmm3 
100267F4  movaps      xmmword ptr [eax+166B0h],xmm0 
100267FB  mov         ebx,dword ptr [esp+4F40h] 
10026802  mov         edi,dword ptr [esp+4F44h] 
10026809  mov         ecx,dword ptr [esp+4F48h] 
10026810  mov         esi,dword ptr [esp+4F4Ch] 
10026817  movss       xmm2,dword ptr [eax+ebx*8+10CF0h] 
10026820  movss       xmm5,dword ptr [eax+edi*8+10CF0h] 
10026829  movss       xmm3,dword ptr [eax+ecx*8+10CF0h] 
10026832  movss       xmm6,dword ptr [eax+esi*8+10CF0h] 
1002683B  unpcklps    xmm2,xmm3 
1002683E  unpcklps    xmm5,xmm6 
10026841  unpcklps    xmm2,xmm5 
10026844  mulps       xmm2,xmm0 
10026847  movaps      xmmword ptr [eax+166B0h],xmm2

在进行分析时,sse 版本在 win 上没有太多好处。

您对如何改进有什么建议吗?预计会有霓虹灯/gcc 的副作用吗?

目前我考虑只对第一部分进行 vecorized 并在循环中进行表读取和插值,希望它将受益于编译器优化。

4

3 回答 3

2

操作系统?然后它与NEON无关。

顺便说一句,NEON 无论如何都无法处理这么大的 LUT。(我对这件事不了解SSE)

首先验证 SSE 是否可以处理这种大小的 LUT,如果可以,我建议使用不同的编译器,因为 GCC 倾向于从内在函数中提取内在函数。

于 2013-07-18T14:28:57.467 回答
1

这是我见过的最糟糕的编译器代码生成(假设启用了优化器)。值得提交一个针对 GCC 的错误。

大部分问题:

  • 分别为每个查找加载val和。val2
  • 分别获取GPRvalval2GPR 的索引。
  • 将索引向量写入堆栈,然后将它们加载到 GPR 中。

为了让编译器生成更好的代码(每个表行加载一次),您可能需要像加载双精度表一样加载每个表行,然后将该行转换为两个浮点数的向量并混合行以获得同质向量。在 NEON 和 SSE 上,这应该只需要 4 次加载和 3 或 4 次拆包(比当前的 8 次加载 + 6 次拆包要好得多)。

摆脱多余的堆栈流量可能更难。确保优化器已打开。修复多负载问题将使堆栈流量减半,因为您只会生成每个索引一次,但要完全摆脱它,可能需要编写程序集而不是内在函数(或使用更新的编译器版本)。

于 2013-07-18T15:39:28.380 回答
0

编译器在这里创建“时髦”代码(有很多重新加载)的原因之一是,为了正确起见,它必须假设sftable[]数组中的数据可能会更改。为了使生成的代码更好,将其重组为如下所示:

VEC_INT iIdx = VEC_FLOAT2INT(m_fIndex);
VEC_FLOAT frac = VEC_SUB(m_fIndex ,VEC_INT2FLOAT(iIdx);
VEC_FLOAT fracnew;

// make it explicit that all you want is _four loads_
typeof(*sftable) tbl[4] = {
    sftable[iIdx[0]], sftable[iIdx[1]], sftable[iIdx[2]], sftable[iIdx[3]]
};

m_fResult[0] = tbl[0].val2
m_fResult[1] = tbl[1].val2;
m_fResult[2] = tbl[2].val2;
m_fResult[3] = tbl[3].val2;
fracnew[0] = tbl[0].val1;
fracnew[1] = tbl[1].val1;
fracnew[2] = tbl[2].val1;
fracnew[3] = tbl[3].val1;

m_fResult=VEC_MUL( m_fResult,frac);
m_fResult=VEC_ADD( m_fResult,fracnew);
frac = fracnew;

使用内在函数可能是有意义的(由于您在 中所拥有的内容的交错布局),因为这两个向量浮点数组很可能都可以通过一条指令加载(在 SSE 中解压缩 hi/lo,在 Neon 中解压缩)。如果没有 AVX2 指令之类的帮助,“主”表查找是不可矢量化的,但它不必超过四个加载。sftable[]fResultfractbl[]VGATHER

于 2013-07-18T16:08:47.547 回答