2016-10-09 8 views
3

이 세 동일한 연산을 수행 할 함수 고려 : (-03 -mavx)와GCC를 사용하여 더 나은 벡터화를 얻으려면 어떻게해야합니까?

#include <x86intrin.h> 

void testfunc_loop(double a, double b, double* dst) 
{ 
    double f[] = {a,b,-a,-b}; 

    for(int n = 0; n < 4; ++n) 
    { 
     dst[n] = 0.1 + f[n]*(1.0 + 0.5*f[n]); 
    } 
} 

void testfunc_flat(double a, double b, double* dst) 
{ 
    dst[0] = 0.1 + (a)*(1.0 + 0.5*(a)); 
    dst[1] = 0.1 + (b)*(1.0 + 0.5*(b)); 
    dst[2] = 0.1 + (-a)*(1.0 + 0.5*(-a)); 
    dst[3] = 0.1 + (-b)*(1.0 + 0.5*(-b)); 
} 

void testfunc_avx(double a, double b, double* dst) 
{ 
    __m256d one  = _mm256_set1_pd(1.0); 
    __m256d half  = _mm256_set1_pd(0.5); 
    __m256d tenth = _mm256_set1_pd(0.1); 

    __m256d v = _mm256_set_pd(-b,-a,b,a); 

    __m256d q = _mm256_add_pd(tenth,_mm256_mul_pd(v,_mm256_add_pd(one,_mm256_mul_pd(half,v)))); 

    _mm256_store_pd(dst,q); 
} 

GCC 4.7.2 루프 버전을 벡터화하지만 펼쳐진 루프 스칼라 연산을 사용한다. 3 가지 버전에서 얻은 (정규화 된) 시간은 3.3 (루프, 자동 벡터화), 1.2 (언롤, 스칼라), 1 (수동 avx)입니다. 언 롤링 된 버전과 수동으로 벡터화 된 기능 간의 성능 차이는 작지만 전체 코드에서 유용하기 때문에 벡터화를 강제하고 싶습니다.

다른 컴파일러 (https://godbolt.org/g/HJH2CX 참조)에서 테스트 한 결과 clang은 풀린 루프를 자동으로 벡터화하지만 (버전 3.4.1부터) GCC는 버전 7까지 자동으로 벡터화합니다. GCC에서 자동으로 비슷한 벡터화를 사용할 수 있습니까? 루프 벡터화와 관련된 최적화 옵션이 도움이되지 않습니다. GCC website은 2011 년 이래로 소식이 없습니다.

+0

누락 된 최적화에 대한 gcc의 버그질라 (bugzilla)에 문제를 제기하는 것이 합당한 방법입니다. 부정 여부가있는 표현식이 gcc와 너무 다르기 때문에 AVX 벡터화가 실패합니다.다른 한편, 그것은 SSE 벡터화를 거의 수행하지만 프롤로그 비용을 크게 과대 평가하기 때문에 수익성이없는 것으로 간주합니다 (사용량 - 비용 - 비용 모델 = 무제한 사용). 즉, 벡터 {a, b } 적어도 세 번, {1,1} 두 번 등). –

+0

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=78164 llvm은 gcc가 아니라 testfunc_flat의 SSE 벡터화를 얻습니다. –

답변

4

gcc는 종종 단일 벡터 항목을 벡터화하지 않습니다. 기존 코드베이스 (Endless Sky)에서 Point{ double x,y; } 클래스를 사용하여 유사한 자동 벡터 생성 기능을 보았습니다.

빠른 코드로 인라인해야 할 경우 x86 용으로 수동으로 벡터화해야 할 수 있습니다. 배열에 저장하는 대신 __m256d 값을 전달하는 것이 좋습니다.

참고 : 수동으로 벡터화 된 버전이 더 빠를 수 있습니다. 나는 Godbolt에서 그걸 가지고 놀았고, _mm256_set_pd(-b,-a, b,a)이 어리석은 코드로 컴파일되고 있다는 것을 알아 차 렸습니다. 그래서 수동으로하는 것이 더 효율적이었습니다. 또한 FMA를 사용할 수없는 경우 표현식을 다시 고려하여 대기 시간을 줄일 수 있습니다. (0.1 -/+ a가 제곱과 평행하게 일어나는 것을 허용). 당신이 시험했을 때 자동 벡터화 루프가 느린 이유


// 0.1 + a + 0.5*a*a = 0.1 + a * (1.0 + 0.5*a) 
//  + b 
// 0.1 - a + 0.5*a*a = 0.1 + (-a) * (1.0 - 0.5*a) 
//  - b 

// only one of the mul+add pairs can fuse into an FMA 
// but 0.1+/-a happens in parallel with 0.5*a*a, so it's lower latency without FMA 
void testfunc_latency_without_fma(double a, double b, double* dst) 
{ 
    // 6 AVX instructions other than the store: 
    // 2 shuffles, 1 mul, 1 FMA, 1 add. 1 xor. In theory could run one iteration per 2 clocks 
    __m256d abab  = _mm256_setr_pd(a, b, a, b); // 1c + 3c latency (unpck + vinsertf128) 
    __m256d sq256  = _mm256_mul_pd(abab, abab);  // 5c 
    const __m256d half = _mm256_set1_pd(0.5); 
    __m256d sq_half256 = _mm256_mul_pd(sq256, half); // 5c: dependency chain 1 ready in 14c from a and b being ready 

    // we could use a smaller constant if we do _mm256_setr_m128d(ab, xor(ab, set1(-0.)) 
    // but that takes an extra vinsertf128 and this part isn't the critical path. 
    const __m256d upper_signmask = _mm256_setr_pd(0. ,0. ,-0. ,-0.); 
    __m256d ab_negab = _mm256_xor_pd(abab, upper_signmask); // chain2: 1c from abab 

    const __m256d tenth = _mm256_set1_pd(0.1); 
    __m256d tenth_plusminus_ab = _mm256_add_pd(tenth, ab_negab); // chain2: 3c (ready way ahead of squared result) 

    __m256d result = _mm256_add_pd(tenth_plusminus_ab, sq_half256); // fuses with the sq_half 
    _mm256_store_pd(dst, result); 
} 
Code+asm here는 IDK. 배열에 스칼라 저장을 수행 한 다음 벡터로드를 수행하여 ~ 11 사이클의 저장소 전달 중지가 발생합니다. 따라서 다른 두 가지 방법보다 지연 시간이 훨씬 길지만 처리량에 영향을 미치는 경우 IDK를 사용하십시오. IDK 어떻게 테스트했는지; 어쩌면 당신은 한 전화의 결과를 다음 전화의 입력으로 사용하고 있었 을까요? 아니면 스택 공간의 동일한 덩어리에서 반복되는 store-forwarding stall이 문제일까요?


일반적으로 큰 배열의 경우 gcc는 실제로 포인터가 정렬되는 것을 좋아합니다. 거대한 완전히 풀린 스칼라 intro/outro 코드를 생성하여 정렬 된 포인터에 도달 한 다음 정렬 된 저장소 /로드를 사용합니다.

이것은 현대 CPU에 많은 도움이되지는 않지만 특히 실행시에 보통에 맞춰진 데이터의 경우에는 큰 도움이되지 않지만 일반적으로 데이터가 정렬되지 않았거나 네 할렘 이전 CPU에서 실행됩니다.

IDK는 gcc가 작은 것들을 자동 벡터화하는 것을 꺼려하지만 double*이 정렬되었다고 말하면 도움이되지 않는다고합니다.

문제의 일부는 셔플이 필요한 코드를 벡터화하기 위해 셔플을 삽입 할 때 좋지 않다고 생각합니다.

+0

철저한 답변 주셔서 감사합니다. 같은 함수를 반복적으로 호출하고 결과를 합산하여 루프의 함수를 테스트했습니다. 수동 벡터화를 고수해야 할 것 같습니다. – stardt