저는 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에서 사용할 수 있습니다.
글쎄, 당신의 손으로 코딩 된 함수가 더 빠르다는 것은 그리 놀라운 일이 아니다. C 컴파일러는 정확한 코드 *를 만들어 내야한다. 루프에는 벡터화 크기의 배수가 아닌 배열 길이에 대한 규정이 없으며 계수가 0인지 여부도 확인하지 않습니다. – EOF
@EOF 네,하지만이 점 옆에 있습니다. 이 벤치 마크의 주요 포인트는 수집 된로드 명령어의 효율과 스칼라로드를 사용하여 동일한 것을 구현하는 것을 비교하는 것이 었습니다. 컴파일러가 생성 한 버전은 시간이 올바른 야간에 있는지, 즉 손으로 코딩 된 버전에서 완전히 바보 같은 짓을하고 있지 않은지 확인하는 데 사용되었습니다. – infinitesimal