2

最適化する次のルックアップと補間コードがあります。(サイズ 128 の float テーブル) 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

プロファイリングを行う場合、win で sse バージョンを使用してもあまりメリットはありません。

改善方法について何か提案はありますか? 予想されるネオン/gcc の副作用はありますか?

現在、最初の部分をベクトル化し、テーブルの読み取りと補間をループで実行することを検討しています。コンパイラの最適化の恩恵を受けることを期待しています。

4

3 に答える 3

2

OSX? それならNEONとは関係ありません。

ところで、とにかく、NEON はこれほど大きな LUT を処理できません。(この件に関してはSSEについては知りません)

SSE がこのサイズの LUT を処理できるかどうかを最初に確認します。処理できる場合は、GCC が組み込み関数から intrinsuck を作成する傾向があるため、別のコンパイラを使用することをお勧めします。

于 2013-07-18T14:28:57.467 に答える
1

これは、私が今まで見た中で最悪のコンパイラ codegen の一部です (オプティマイザが有効になっていると仮定して)。GCC に対してバグを報告する価値があります。

主要課題:

  • ロードvalval2ルックアップごとに個別に。
  • GPRのインデックスvalval2GPR へのインデックスを別々に取得します。
  • インデックスのベクトルをスタックに書き込み、GPR にロードします。

コンパイラがより良いコードを生成するようにする (各テーブル行に対して 1 回のロード) には、各テーブル行を double であるかのようにロードし、次に行を 2 つの float のベクトルにキャストし、行をスウィズルして均質にする必要がある場合があります。ベクトル。NEON と SSE の両方で、これには 4 つのロードと 3 つまたは 4 つのアンパックしか必要ありません (現在の 8 つのロード + 6 つのアンパックよりもはるかに優れています)。

余分なスタック トラフィックを取り除くのは難しいかもしれません。オプティマイザがオンになっていることを確認します。複数の読み込みの問題を修正すると、各インデックスを 1 回しか生成しないため、スタック トラフィックが半分になりますが、それを完全に取り除くには、組み込み関数の代わりにアセンブリを記述する (または新しいコンパイラ バージョンを使用する) 必要がある場合があります。

于 2013-07-18T15:39:28.380 に答える
0

ここでコンパイラが (多くの再読み込みを伴う) 「ファンキーな」コードを作成する理由の 1 つは、正確さのために、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;

組み込み関数を使用することは ( にあるもののインターリーブレイアウトのため)理にかなっている可能性があります。これは、ベクトル float 配列と両方が単一の命令 (SSE で hi/lo をアンパックし、Neon で unzip) でロードできる可能性が非常に高いためです。「メイン」テーブルのルックアップは、AVX2 の命令などの助けなしではベクトル化できませんが、4 つ以上のロードである必要はありません。sftable[]fResultfractbl[]VGATHER

于 2013-07-18T16:08:47.547 に答える