2014-07-15 7 views
16

저는 AVX2 명령어 세트의 새로운 수집 명령어 사용을 조사했습니다. 특히, 하나의 부동 소수점 배열이 대체되고 다른 부동 소수점 배열에 추가되는 간단한 문제를 벤치마킹하기로 결정했습니다. c에서이 값은AVX2는 어떤 상황에서 데이터를 개별적으로로드하는 것보다 빠른 속도로 데이터를 수집합니까?

void vectortest(double * a,double * b,unsigned int * ind,unsigned int N) 
{ 
    int i; 
    for(i=0;i<N;++i) 
    { 
     a[i]+=b[ind[i]]; 
    } 
} 

과 같이 구현할 수 있습니다. g ++ -O3 -march = native로이 함수를 컴파일합니다. 이제 저는 이것을 세 가지 방식으로 구현합니다. 간단히하기 위해 배열 N의 길이가 4로 나눌 수 있다고 가정합니다. 간단한, 비 벡터화 구현 :

loop: sub rcx, 4 

     mov eax,[rdx+rcx*4] ;first load the values from array b to xmm1-xmm4 
     vmovq xmm1,[rsi+rax*8] 
     mov eax,[rdx+rcx*4+4] 
     vmovq xmm2,[rsi+rax*8] 

     mov eax,[rdx+rcx*4+8] 
     vmovq xmm3,[rsi+rax*8] 
     mov eax,[rdx+rcx*4+12] 
     vmovq xmm4,[rsi+rax*8] 

     vmovlhps xmm1,xmm2  ;now collect them all to ymm1 
     vmovlhps xmm3,xmm4 
     vinsertf128 ymm1,ymm1,xmm3,1 

     vaddpd ymm1, ymm1, [rdi+rcx*8] 
     vmovupd [rdi+rcx*8], ymm1 

     cmp rcx, 0 
     jne loop 

그리고 마지막으로, 구현이 vgatherdpd 사용 :

loop: sub rcx, 4    
     vmovdqu xmm2,[rdx+4*rcx]   ;load the offsets from array ind to xmm2 
     vpcmpeqw ymm3,ymm3     ;set ymm3 to all ones, since it acts as the mask in vgatherdpd                                         
     vgatherdpd ymm1,[rsi+8*xmm2],ymm3 ;now gather the elements from array b to ymm1 

     vaddpd ymm1, ymm1, [rdi+rcx*8] 
     vmovupd [rdi+rcx*8], ymm1 

     cmp rcx, 0 
     jne loop 

I 벤치 마크에서 이러한 기능을

align 4 
global vectortest_asm 
vectortest_asm: 
     ;; double * a = rdi                                                         
     ;; double * b = rsi                                                         
     ;; unsigned int * ind = rdx                                                       
     ;; unsigned int N = rcx                                                        

     push rax 
     xor rax,rax 

loop: sub rcx, 1 
     mov eax, [rdx+rcx*4] ;eax = ind[rcx]                                                     
     vmovq xmm0, [rdi+rcx*8]   ;xmm0 = a[rcx]                                                   
     vaddsd xmm0, [rsi+rax*8]  ;xmm1 += b[rax] (and b[rax] = b[eax] = b[ind[rcx]])                                         
     vmovq [rdi+rcx*8], xmm0 
     cmp rcx, 0 
     jne loop 

     pop rax 

     ret 

는 루프가 수집 명령없이 벡터화 Haswell cpu가있는 기계 (Xeon E3-1245 v3). 몇 가지 일반적인 결과 (초 단위) :

Array length 100, function called 100000000 times. 
Gcc version: 6.67439 
Nonvectorized assembly implementation: 6.64713 
Vectorized without gather: 4.88616 
Vectorized with gather: 9.32949 

Array length 1000, function called 10000000 times. 
Gcc version: 5.48479 
Nonvectorized assembly implementation: 5.56681 
Vectorized without gather: 4.70103 
Vectorized with gather: 8.94149 

Array length 10000, function called 1000000 times. 
Gcc version: 7.35433 
Nonvectorized assembly implementation: 7.66528 
Vectorized without gather: 7.92428 
Vectorized with gather: 8.873 

gcc와 벡터화되지 않은 어셈블리 버전은 서로 매우 근접합니다. (gcc의 어셈블리 출력도 확인했습니다. 손으로 코딩 한 버전과 매우 유사합니다.) 벡터화는 작은 배열에 약간의 이점을 제공하지만 큰 배열에 대해서는 느립니다. 가장 큰 놀라움은 적어도 vgatherpdp를 사용하는 버전이 너무 느리다는 것입니다. 그래서, 제 질문은, 왜죠? 나는 바보 같은 짓을하고 있니? 누군가가 수집 명령이 실제로 여러로드 작업을 수행하는 것 이상의 성능 이점을 제공하는 예제를 제공 할 수 있습니까? 그렇지 않다면 실제로 그러한 지시를받는 것이 무엇입니까?

g ++ 및 nasm 용 메이크 파일이있는 테스트 코드는 https://github.com/vanhala/vectortest.git에서 사용할 수 있습니다.

+0

글쎄, 당신의 손으로 코딩 된 함수가 더 빠르다는 것은 그리 놀라운 일이 아니다. C 컴파일러는 정확한 코드 *를 만들어 내야한다. 루프에는 벡터화 크기의 배수가 아닌 배열 길이에 대한 규정이 없으며 계수가 0인지 여부도 확인하지 않습니다. – EOF

+0

@EOF 네,하지만이 점 옆에 있습니다. 이 벤치 마크의 주요 포인트는 수집 된로드 명령어의 효율과 스칼라로드를 사용하여 동일한 것을 구현하는 것을 비교하는 것이 었습니다. 컴파일러가 생성 한 버전은 시간이 올바른 야간에 있는지, 즉 손으로 코딩 된 버전에서 완전히 바보 같은 짓을하고 있지 않은지 확인하는 데 사용되었습니다. – infinitesimal

답변

8

불행히도 수집 된로드 명령어는 특히 "똑똑"하지 않습니다.로드 주소에 관계없이 요소 당 하나의 버스 사이클을 생성하는 것처럼 보입니다. 따라서 연속 요소가있는 경우에도 해당 요소를 통합하는 내부 논리가 없습니다. 잔뜩. 따라서 효율성 측면에서 수집 된로드는 하나의 명령어 만 사용한다는 점을 제외하면 N 스칼라로드보다 우수합니다.

수집 지침의 유일한 이점은 SIMD 코드를 구현할 때 발생하며 추가 SIMD 작업을 적용 할 비 연속 데이터를로드해야하는 경우입니다. 이 경우에, SIMD 수집 된로드 명령은 통상적으로 예를 들어,도 1에 의해 생성 될 스칼라 코드의 묶음보다 훨씬 더 효율적일 것이다. _mm256_set_xxx() (또는 실제 액세스 패턴에 따라 인접로드 및 순열 등).

+1

나는 후자를 이해할 수 없다.위의 예제에서 배열 b의 비 연속 데이터를로드 한 다음 해당 데이터에 일부 SIMD 명령어를 적용합니다. 이 경우 수집 명령을 스칼라 mov로 바꾸면 더 빠른 코드가 생성됩니다. 수집 된로드가 여러 스칼라로드보다 느리지 않은 실제 벤치 마크 또는 예제에 대한 포인터를 제공 할 수 있습니까? 또는 컴파일러가 gather 명령어를 사용하여 코드를 생성하는 것이 더 쉽다는 것을 의미합니까? – infinitesimal

+0

위의 주석의 마지막 문장에서 "느린"은 "스칼라 명령어를 사용하여 코드를로드하는 것보다 손으로 만들어진 것보다 느리다"는 것을 의미합니다. – infinitesimal

+0

예를 들어 내가 사용하는 일반적인 경우에 대해 이야기하고있었습니다. '_mm256_set_xxx'는 수집 된로드를 사용하는 대신 N 개의 분산 된 값을로드합니다. 일반적으로 컴파일러는 이전 스 do 어를 수행하는 스칼라 코드를 많이 생성합니다. 귀하의 예제는 특정 유스 케이스에 대해 일부 어셈블러를 직접 코딩 한 점에서 다소 다릅니다. 그것은 또한 요소의 수에 달려 있습니다. 저는 수집 된로드의 문제가 32/64 비트 요소보다 다소 큰 8 비트 및 16 비트 데이터 (물론 AVX2에서)로 주로 작업합니다. –