2012-02-13 4 views
1

인텔 컴파일러에서 자동 벡터화를 사용하고 sse를 사용하여 일부 코드의 속도를 높이려고합니다. 모든 계산은 node_t 구조체를 다른 구조체 w_t (tr() 및 gen_tr())로 변환하는 변형입니다. gen_tr() 함수를 벡터화 할 때 어떤 효과도 발생하지 않습니다.자동 벡터화 및 sse를 사용하여 데이터 크기에 종속 속도 향상

데이터 저장 형식을 변경하면 각 struct 구성 요소가 부동 소수점 배열에 저장 될 때 자동 벡터화가 잘 작동합니다 (function genv_tr() 참조).

sse를 사용하는 함수는 ssev_tr (N은 4로 균등하게 나눠야 함)이라고하는 함수입니다.

transform.c :

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

static __inline__ unsigned long getCC(void) 
{ 
    unsigned a, d; 
    asm volatile("rdtsc" : "=a" (a), "=d" (d)); 
    return ((unsigned long)a) | (((unsigned long)d) << 32); 
} 

typedef struct { 
    float x1, x2, x3, x4, x5; 
} node_t; 

typedef struct { 
    float w1, w2, w3, w4; 
} w_t; 

void tr(node_t *n, float c1, float c2, w_t *w) 
{ 
    const float nv = n->x1; 
    const float N00T = n->x3 * c1; 

    const float n1v = n->x2; 
    const float N01T = n->x4 * c2; 

    w->w1 = nv - N00T; 
    w->w2 = nv + N00T; 
    w->w3 = n1v - N01T; 
    w->w4 = n1v + N01T; 
} 

__attribute__ ((noinline)) 
void gen_tr(node_t *n, w_t *w, const int N, float c1, float c2) 
{ 
    int i; 
    #pragma vector aligned 
    #pragma ivdep 
    for (i = 0; i < N; i++) { 
     tr(n + i, c1, c2, w + i); 
    } 
} 

__attribute__ ((noinline)) 
void genv_tr(float *x1, float *x2, float *x3, float *x4, float *x5, float *w1, float *w2, float *w3, float *w4, const int N, float c1, float c2) 
{ 
    int i; 
    #pragma vector aligned 
    #pragma ivdep 
    for (i = 0; i < N; i++) { 
     const float N00T = x3[i] * c1; 
     const float N01T = x4[i] * c2; 

     w1[i] = x1[i] - N00T; 
     w2[i] = x1[i] + N00T; 
     w3[i] = x2[i] - N01T; 
     w4[i] = x2[i] + N01T; 
    } 
} 

__attribute__ ((noinline)) 
void ssev_tr(float *x1, float *x2, float *x3, float *x4, float *x5, float *w1, float *w2, float *w3, float *w4, const int N, float c1, float c2) 
{ 
    __m128 *ws1 = (__m128*)w1; 
    __m128 *ws2 = (__m128*)w2; 
    __m128 *ws3 = (__m128*)w3; 
    __m128 *ws4 = (__m128*)w4; 

    __m128 *xs1 = (__m128*)x1; 
    __m128 *xs2 = (__m128*)x2; 
    __m128 *xs3 = (__m128*)x3; 
    __m128 *xs4 = (__m128*)x4; 

    const __m128 cs1 = _mm_set1_ps(c1); 
    const __m128 cs2 = _mm_set1_ps(c2); 

    int i; 
    #pragma vector aligned 
    #pragma ivdep 
    for (i = 0; i < N/4; i++) { 
     const __m128 N00T = _mm_mul_ps(xs3[i], cs1); 
     const __m128 N01T = _mm_mul_ps(xs4[i], cs2); 

     ws1[i] = _mm_sub_ps(xs1[i], N00T); 
     ws2[i] = _mm_add_ps(xs1[i], N00T); 
     ws3[i] = _mm_sub_ps(xs2[i], N01T); 
     ws4[i] = _mm_add_ps(xs2[i], N01T); 
    } 
} 

#define test(func) \ 
    for (i = 0; i < n; i++) { \ 
     x[i].x1 = 1.0; \ 
     x[i].x2 = 2.0; \ 
     x[i].x3 = 2.0; \ 
     x[i].x4 = 2.0; \ 
     x[i].x5 = 2.0; \ 
    } \ 
    \ 
    t1 = getCC(); \ 
    for (i = 0; i < rep; i++) { \ 
     func(x, w, n, c1, c2); \ 
    } \ 
    t2 = getCC(); \ 
    printf("\t%f", ((double)(t2 - t1))/n/rep); 

#define test1(func) \ 
    for (i = 0; i < n; i++) { \ 
     x1[i] = 1.0; \ 
     x2[i] = 2.0; \ 
     x3[i] = 2.0; \ 
     x4[i] = 2.0; \ 
     x5[i] = 2.0; \ 
    } \ 
    \ 
    t1 = getCC(); \ 
    for (i = 0; i < rep; i++) { \ 
     func(x1, x2, x3, x4, x5, w1, w2, w3, w4, n, c1, c2); \ 
    } \ 
    t2 = getCC(); \ 
    printf("\t%f", ((double)(t2 - t1))/n/rep); 

int main(int argc, char *argv[]) 
{ 
    if (argc < 2) { 
     printf("Usage %s vector_size\n", argv[0]); 
    } 
    int n = atoi(argv[1]); 
    printf("%d", n); 
    int rep = 100000000/n; 
    int i; 
    int inc = 1; 
    float c1 = 2.0, c2 = 1.0; 
    unsigned long t1, t2; 
    node_t *x = (node_t*)malloc(n * sizeof(node_t)); 
    w_t *w = (w_t*)malloc(n * sizeof(w_t)); 

    float *x1 = (float*)malloc(n * sizeof(float)); 
    float *x2 = (float*)malloc(n * sizeof(float)); 
    float *x3 = (float*)malloc(n * sizeof(float)); 
    float *x4 = (float*)malloc(n * sizeof(float)); 
    float *x5 = (float*)malloc(n * sizeof(float)); 

    float *w1 = (float*)malloc(n * sizeof(float)); 
    float *w2 = (float*)malloc(n * sizeof(float)); 
    float *w3 = (float*)malloc(n * sizeof(float)); 
    float *w4 = (float*)malloc(n * sizeof(float)); 

    test(gen_tr); 
    test1(genv_tr); 
    test1(ssev_tr); 

    printf("\n"); 
    return 0; 
} 

컴파일 옵션 : ICC -03 -Wall -W -vec - report6의 transform.c는 ICC의

버전 변환 -o - 12.1.2, OS를 - 페도라 16 x86_64, CPU - Intel Core2 Quad CPU Q8200.

그런 다음 내가 64 단계와 16에서 3000까지 다양한 크기와 실행, 여기 스크립트 : 여기

#!/bin/bash 

echo "" > run.log 

for ((c=16;c<3000;c+=64)) 
do 
./transform $c | tee -a run.log 
done 

작업이 스크립트 (크기, gen_tr, genv_tr, ssev_tr)의 일부 결과, 모든 시간은 당 표시 하나의 배열 요소 :

16  7.710743  3.168577  3.253829 
272  7.166493  1.983918  2.618569 
528  7.121866  1.920195  2.567109 
784  7.115007  1.899451  2.549645 
1040 8.104026  2.481062  2.944317 
1296 8.137537  5.105032  5.104614 
1552 8.118534  5.068812  5.064211 
1808 8.138309  5.077831  5.085015 
2064 8.149699  5.107503  5.069958 
2320 8.164556  5.080981  5.099313 
2576 8.151524  5.086056  5.089294 
2832 8.212946  5.061927  5.072261 

벡터화 된 버전의 함수를 사용할 때 왜 크기가 매우 중요한가? 캐시 미스 때문에 그것을합니까? 모든 데이터 범위에서 동일한 속도를 유지할 수 있습니까?

답변

1

8 개의 플로트 배열이 있습니다. 크기가 1000 인 경우 약 32kB의 데이터를 조작합니다. L1 캐시가 약간 더 크지 만 (64kB), L1 캐시는 연관성으로 인해 동시에 모든 32kB 데이터를 보유하지 못할 가능성이 높습니다.

테스트가 반복되어 동일한 데이터를 반복하여 테스트합니다. 두 경우를 생각해

  • 크기 = 528 : 8 개 배열 편리하게 L1 캐시에 맞게. 각 테스트 반복 (첫 번째 테스트 반복 제외)은 데이터에 빠르게 액세스 할 수 있습니다.
  • 크기 = 1268 : 8 개의 어레이가 동시에 L1 캐시에 맞지 않습니다. 각 테스트 반복은 L1에서 데이터를 계속 내보내므로 효과적으로 모든 읽기와 쓰기가 L2로 이동합니다.

따라서 입력 크기 1000에서의 점프는 부분적으로 테스트의 아티팩트이지만 전부는 아닙니다. 현실 세계에서 L1 캐시에 필요한 모든 데이터를 이미 확보 한 경우 genv_tr은 매우 빠릅니다. 그러나 크기가 1000보다 큰 입력에서는 모든 입력이 단순히 L1 캐시에 맞지 않으므로 일부 액세스는 확실히 L2로 이동합니다.