2014-10-14 2 views
4

SSE 명령어로 벡터화가 어떻게 작동하는지 이해하려고합니다. 여기SSE 명령어로 벡터화 이해하기

벡터화이 달성 된 코드 :

#include <stdlib.h> 
#include <stdio.h> 

#define SIZE 10000 

void test1(double * restrict a, double * restrict b) 
{ 
    int i; 

    double *x = __builtin_assume_aligned(a, 16); 
    double *y = __builtin_assume_aligned(b, 16); 

    for (i = 0; i < SIZE; i++) 
    { 
    x[i] += y[i]; 
    } 
} 

내 컴파일 명령 : 여기

gcc -std=c99 -c example1.c -O3 -S -o example1.s 

어셈블러 코드의 출력 :

.file "example1.c" 
    .text 
    .p2align 4,,15 
    .globl test1 
    .type test1, @function 
test1: 
.LFB7: 
    .cfi_startproc 
    xorl %eax, %eax 
    .p2align 4,,10 
    .p2align 3 
.L3: 
    movapd (%rdi,%rax), %xmm0 
    addpd (%rsi,%rax), %xmm0 
    movapd %xmm0, (%rdi,%rax) 
    addq $16, %rax 
    cmpq $80000, %rax 
    jne .L3 
    rep ret 
    .cfi_endproc 
.LFE7: 
    .size test1, .-test1 
    .ident "GCC: (Debian 4.8.2-16) 4.8.2" 
    .section .note.GNU-stack,"",@progbits 

내가 어셈블러 연습 한 몇 년 전 그리고 위에 무엇이 나타 났는지 알고 싶습니다 레지스터 % rdi, % rax 및 % rsi.

% xmm0은 2 배 (16 바이트)를 저장할 수있는 SIMD 레지스터입니다.

는하지만 동시에 또한이 수행되는 방법을 이해하지 않습니다

을 내가 모두가 여기 일이 생각 :

 movapd (%rdi,%rax), %xmm0 
     addpd (%rsi,%rax), %xmm0 
     movapd %xmm0, (%rdi,%rax) 
     addq $16, %rax 
     cmpq $80000, %rax 
     jne .L3 
     rep ret 

이 %의 RAX가 나타내는 하는가 "X"배열을?

% rsi는 C 코드 조각에서 무엇을 나타 냅니까?

이 [0]이 [0] + B를 = 예를 들어, 최종 결과 (하는가 [0] %의 RDI에 저장?

감사 도와

+1

64 비트 코드를 생성 중이며 rXX 레지스터는 일반적인 x86 레지스터의 64 비트 버전입니다 (예 : eax = 32 비트, rax = 64 비트). –

+0

'-fverbose-asm'을 사용하면 gcc가 각 명령어에 주석을 달아 각 피연산자의 이름을 부여 할 수 있습니다. (종종 tmp 이름에 번호가 매겨 지지만 때때로 의미있는 C 변수 이름을 얻습니다. 특히 배열 색인 생성에 사용됩니다.) –

답변

5

알아 두어야 할 첫 번째 일은 유닉스 시스템에서 64 비트 코드에 대한 호출 규칙입니다. Wikipedia's x86-64_calling_conventions을 참조하십시오. 자세한 내용은 Agner Fog의 calling conventions manual을 참조하십시오.

정수 매개 변수는 rdi, rsi, rdx, rcx, r8, r9 순서로 전달됩니다. 따라서 등록기별로 6 개의 정수 값을 전달할 수 있습니다 (단, Windows에서는 4 개만 허용). 이것은 귀하의 경우 의미 :

rdi = &x[0], 
rsi = &y[0]. 

레지스터 rax 0에서 시작하고 증가 2*sizeof(double)=16 바이트 각 반복. 그런 다음 루프가 완료되었는지를 테스트하기 위해 각 반복마다 sizeof(double)*10000=80000과 비교됩니다.

여기서 cmp의 사용은 실제로 GCC 컴파일러에서 비효율적입니다. 현대 인텔 프로세서는 하나의 명령에 cmpjne 지시를 융합 할 수 있으며 그들은 또한 하나 개의 명령어로 addjne을 융합 할 수 있지만, 수 없습니다 퓨즈 add, cmp, 하나의 명령으로 jne. 하지만 remove the cmp instruction 일 수 있습니다.

rdi = &x[0] + 80000; 
rsi = &y[0] + 80000; 
rax = -80000 

을 설정 한 후 루프가 0까지 이제 -80000에서 루프 카운트이

movapd (%rdi,%rax), %xmm0  ; temp = x[i] 
addpd (%rsi,%rax), %xmm0   ; temp += y[i] 
movapd %xmm0, (%rdi,%rax)  ; x[i] = temp 
addq $16, %rax     ; i += 2 
jnz .L3       ; then loop 

같이 할 수 했어야과 cmp 교육을 필요로하지 않는다 무엇 GCC

addjnz은 하나의 마이크로 작업으로 통합됩니다.

3
movapd (%rdi,%rax), %xmm0  ; temp = x[i] 
    addpd (%rsi,%rax), %xmm0   ; temp += y[i] 
    movapd %xmm0, (%rdi,%rax)  ; x[i] = temp 
    addq $16, %rax     ; i += 2 
    cmpq $80000, %rax    ; if (i < SIZE) 
    jne .L3       ; then loop 

RAX 레지스터 나타낸다 i 변수가 있지만, 바이트 인덱스로서 저장, RDI는 RSI은 & Y이다 & X이다. 각 루타, 2 *를 sizeof (double)에 의해 RAX의 따라서 증가 또는 16 바이트를 가산 루프 통과.