1

첫째로, 필자는 내 문제를 해결하기 위해 아래 코드의 기능을 이해해야한다고 생각하지 않는다고 말하면서 접두사를 붙입니다. 이것은 주로 최적화 문제입니다. 이 코드는 수행중인 작업을 이해하는 것입니다.컴파일러가 루프 외부에서 레지스터를 설정하도록 유도

I있다 (동작) 다음 다소 최적화 컨벌루션 메인 루프 :

for(int i=0; i<length-kernel_length; i+=4){ 

    acc = _mm_setzero_ps(); 

    for(int k=0; k<KERNEL_LENGTH; k+=4){ 

     int data_offset = i + k; 

     for (int l = 0; l < 4; l++){ 

      data_block = _mm_load_ps(in_aligned[l] + data_offset); 
      prod = _mm_mul_ps(kernel_reverse[k+l], data_block); 

      acc = _mm_add_ps(acc, prod); 
     } 
    } 
    _mm_storeu_ps(out+i, acc); 

} 

KERNEL_LENGTH는 각각, 제 in_aligned 입력 어레이 (이 때 콘볼 루션을 수행한다) 반복 4 배이다 반복은 한 샘플을 다른 샘플에서 왼쪽으로 이동합니다. 16 바이트로 정렬 된 위치에서 모든 샘플을 찾을 수 있습니다.

float kernel_block[4] __attribute__ ((aligned (16))); 
__m128 kernel_reverse[KERNEL_LENGTH] __attribute__ ((aligned (16)));  

// Repeat the kernel across the vector 
for(int i=0; i<KERNEL_LENGTH; i++){ 
    kernel_block[0] = kernel[kernel_length - i - 1]; 
    kernel_block[1] = kernel[kernel_length - i - 1]; 
    kernel_block[2] = kernel[kernel_length - i - 1]; 
    kernel_block[3] = kernel[kernel_length - i - 1]; 

    kernel_reverse[i] = _mm_load_ps(kernel_block); 
} 

코드는 정확하고 꽤 빨리 너무 알고리즘을 계산 : kernel_reverse 모든 샘플 4 벡터를 채우기 위해 4 번 반복하고 선언으로 정의와, 반전 된 커널입니다. 루프 아래의 기계 코드로 컴파일 :

나는 내 질문은 이것이다 gcc -std=c99 -Wall -O3 -msse3 -mtune=core2

와 코드를 컴파일합니다. 이 루프 안에는 커널을 매번로드하는 데별로 중요하지 않은 명령어가 필요합니다. 커널은 루프 반복마다 변경되지 않으므로 원칙적으로 SSE 레지스터에 보관할 수 있습니다. 내가 알기에, 커널을 쉽게 저장할 수있는 충분한 레지스터가있다. (실제로, 기계 코드는 너무 많은 레지스터 압박을 제시하지 않는다.)

모든 루프에서 커널을로드하지 않도록 컴파일러를 설득하려면 어떻게합니까?

커널 길이를 일정하게 설정하면 컴파일러에서 자동으로이 작업을 수행 할 것으로 예상했습니다.

testl %edx, %edx 
    jle .L79 
    leaq (%rcx,%rcx,2), %rsi 
    movaps -144(%rbp), %xmm6 
    xorps %xmm2, %xmm2 
    leal -1(%rdx), %ecx 
    movaps -128(%rbp), %xmm5 
    xorl %eax, %eax 
    movaps -112(%rbp), %xmm4 
    leaq 0(%r13,%rsi,4), %rsi 
    shrl $2, %ecx 
    addq $1, %rcx 
    movaps -96(%rbp), %xmm3 
    salq $4, %rcx 
    .p2align 4,,10 
    .p2align 3 
.L80: 
    movaps 0(%r13,%rax), %xmm0 
    movaps (%r14,%rax), %xmm1 
    mulps %xmm6, %xmm0 
    mulps %xmm5, %xmm1 
    addps %xmm2, %xmm0 
    addps %xmm1, %xmm0 
    movaps (%r9,%rax), %xmm1 
    mulps %xmm4, %xmm1 
    addps %xmm1, %xmm0 
    movaps (%rsi,%rax), %xmm1 
    mulps %xmm3, %xmm1 
    addps %xmm1, %xmm0 
    movups %xmm0, (%rbx,%rax) 
    addq $16, %rax 
    cmpq %rcx, %rax 
    jne .L80 
.L79: 

편집 : 전체 코드 목록은 다음과 같다 :

#define KERNEL_LENGTH 4 
int convolve_sse_in_aligned_fixed_kernel(float* in, float* out, int length, 
     float* kernel, int kernel_length) 
{ 
    float kernel_block[4] __attribute__ ((aligned (16))); 
    float in_aligned[4][length] __attribute__ ((aligned (16))); 

    __m128 kernel_reverse[KERNEL_LENGTH] __attribute__ ((aligned (16)));  
    __m128 data_block __attribute__ ((aligned (16))); 

    __m128 prod __attribute__ ((aligned (16))); 
    __m128 acc __attribute__ ((aligned (16))); 

    // Repeat the kernel across the vector 
    for(int i=0; i<KERNEL_LENGTH; i++){ 
     int index = kernel_length - i - 1; 
     kernel_block[0] = kernel[index]; 
     kernel_block[1] = kernel[index]; 
     kernel_block[2] = kernel[index]; 
     kernel_block[3] = kernel[index]; 

     kernel_reverse[i] = _mm_load_ps(kernel_block); 
    } 

    /* Create a set of 4 aligned arrays 
    * Each array is offset by one sample from the one before 
    */ 
    for(int i=0; i<4; i++){ 
     memcpy(in_aligned[i], (in+i), (length-i)*sizeof(float)); 
    } 

    for(int i=0; i<length-kernel_length; i+=4){ 

     acc = _mm_setzero_ps(); 

     for(int k=0; k<KERNEL_LENGTH; k+=4){ 

      int data_offset = i + k; 

      for (int l = 0; l < 4; l++){ 

       data_block = _mm_load_ps(in_aligned[l] + data_offset); 
       prod = _mm_mul_ps(kernel_reverse[k+l], data_block); 

       acc = _mm_add_ps(acc, prod); 
      } 
     } 
     _mm_storeu_ps(out+i, acc); 

    } 

    // Need to do the last value as a special case 
    int i = length - kernel_length; 
    out[i] = 0.0; 
    for(int k=0; k<kernel_length; k++){ 
     out[i] += in_aligned[0][i+k] * kernel[kernel_length - k - 1]; 
    } 

    return 0; 
} 
+0

'gcc -S'의 출력물이 객체 파일을 분해하는 것보다 약간 더 읽기 쉽다는 것을 알았습니다. –

+0

'i'가 상수가 아니기 때문에 배열 인덱스를 미리 계산하고, 루프의 시작 부분에 int index = kernel_length - i - 1;을 설정하여 실험하고,'kernel_block [0] = kernel [색인]; 등등. – Lundin

+0

@PascalCuoq 의견에 따르면, 나는'gcc -S'의 결과물을 사용하는 것으로 바꿨다. 이것은 내가 제안하는 루프를 _ 내 _ 내게 제안합니다. 나는이 주장을 확인하는 누군가의 온전함에 정말로 감사 할 것입니다. –

답변

0

대답은, 그것은 내가 원하는 정확히 무엇을하고있다. 문제는 내게는 출력이 objdump -d에서 읽지 못하는 것입니다. @PascalCuoq에서 제안한대로 gcc -S의 출력을 사용하도록 질문을 수정하는 과정에서 루프가 훨씬 쉽게 이해됩니다.

누군가가 그 점을 소중하게 생각하기 때문에 질문을 떠났습니다! (그리고 실제로 코드).

관련 문제