2013-07-18 11 views
2

나는 다음과 같은 조회 및 보간 코드를 최적화해야합니다. (128 사이즈 플로트 테이블) Windows에서는 인텔 컴파일러, OSX에서는 GCC, 네온 OSX에서는 GCC와 함께 사용됩니다.SSE/NEON 테이블 조회 최적화

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했다. 내가 테이블 액세스와 정렬 메모리로 이동이 실제 병목 현상이 여기에 있다고 생각

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); 

(매크로는 SSE/네온 명령어로 변환). 나는 어셈블러와 잘 모르지만 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 버전과 많은 혜택이없는 프로파일 링.

개선 할만한 방법이 있습니까? 네온/gcc가 부작용이 예상됩니까?

현재 저는 첫 번째 부분을 vecorized로 만들고 컴파일러 최적화의 이점이되기를 희망하면서 루프에서 테이블 판독 및 보간을 수행하는 것을 고려합니다.

+0

그냥 보면 첫 번째 코드 집합을 작성하는 데 매우 긴 명령 집합처럼 보입니다. –

+0

이렇게하기 위해서, AVX2 (! - Haswell 만)는 큰 (메모리 내) 테이블의 검색을 위해'VGATHER' 명령어 군을 제공 받았습니다. 인텔에서는 SSE/AVX의 셔플을 통해 작은 테이블 검색을 수행 할 수 있습니다 (단, 부동 테이블의 경우 4/8 이하). SSE/AVX 규정에 맞지 않는 항목이 테이블에있는 경우에만 가능합니다. ARM NEON은 VTBL을 통해 동일한 작업을 수행 할 수 있습니다 (테이블은 최대 4 개의 네온 규정에 포함될 수 있으므로 다시 8 개의 부동 소수점에 포함될 수 있습니다). –

+0

불행히도 최대 호환성이 필요하므로 나에게 최적화가 필요하지 않습니다. – evitan

답변

2

OSX? 그러면 NEON과 아무 관련이 없습니다.

BTW, 네온은이 큰 LUT를 처리 할 수 ​​없습니다. (이 문제에 대해 SSE에 대해 모르겠다.)

SSE가이 크기의 LUT를 처리 할 수 ​​있는지 먼저 확인하고, 그렇다면 GCC가 intrinsics에서 intrinsuck을 없애기 때문에 다른 컴파일러를 사용하는 것이 좋습니다.

+0

잘 코드가 다양한 플랫폼에서 실행되며 osx에서 컴파일하는 네온 벡터 코드가 포함된다는 것을 분명히 밝히고 싶습니다. – evitan

1

그건 내가 본 중 최악의 컴파일러 코드gen 중 일부입니다 (옵티 마이저가 활성화되었다고 가정). GCC에 버그를 제기 할만한 가치가 있습니다.

주요 문제 :

  • 로드 val 별도로 각 조회에 대한 val2.
  • valval2에 대한 색인을 GPR로 별도 취득.
  • 인덱스 벡터를 스택에 쓴 다음 GPR에로드합니다.

더 나은 코드 (각 테이블 행당 하나의로드)를 컴파일러에서 얻으려면 각 테이블 행을 두 행으로로드 한 다음 두 행 수의 벡터로 행을 캐스팅해야 할 수도 있습니다. 균질 한 벡터를 얻기 위해 선들을 뒤섞다. NEON과 SSE 모두에서이 작업에는 네 개의로드와 세 개 또는 네 개의 언팩이 필요합니다 (현재 8 개로드 + 6 개 언 팩보다 훨씬 좋음).

불필요한 스택 트래픽을 제거하는 것이 더 어려울 수 있습니다. 옵티 마이저가 켜져 있는지 확인하십시오. 다중로드 문제를 해결하면 각 인덱스를 한 번만 생성하기 때문에 스택 트래픽이 절반으로 줄어들지 만 내장 함수 대신 어셈블리를 작성하거나 새로운 컴파일러 버전을 사용해야 할 수도 있습니다. 컴파일러는 sftable[] 배열 의 데이터가를 변경할 수 있음을, 정확성, 여기에 (재로드의 많은) "펑키"코드를 작성하고, 가정해야하기 때문입니다 이유 중

+0

또한 _change the source_. 메모리 밑에있는 메모리가 변경되지 않는다는 점을 분명히하지 않았기 때문에 필연적 인 (다시로드하기) 컴파일러를 비난하는 것은 공정하지 않습니다 ... –

+0

@FrankH .: (테이블이 휘발성으로 표시되고'm_fResult'는 로컬입니다. 거의 확실합니다) 컴파일러는 테이블 항목이 그 아래에서 변경되지 않을 것이라고 가정해야합니다. 즉, 소스가 더 깔끔하게 작성 될 수 있습니다 (필자는 대답에서이 작업을 수행하는 방법을 제안했습니다). –

+0

'm_fResult'에는 해당하지만'sftable []'자체에는 _NOT_입니다. –

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; 

벡터 모두 플로트 배열 fResultfrac 때문에 하나의 명령으로 tbl[]에서로드 할 수 있습니다 (SSE에서는 hi/lo를 풀고 Neon에서는 압축을 풀어). "주"테이블 조회는 AVX2의 VGATHER 명령어와 같은 도움없이 벡터화 할 수 없지만 4 개를 초과 할 필요는 없습니다.

+0

나는 그걸로 주위를 둘러 보겠습니다. 내 주요 오류는 비 로컬 메모리 (m_fResult 및 m_fIndex)를 사용하는 것이 었습니다. 내가 그들을 현지화하면 무엇이 나오는지 보겠습니다. – evitan