2014-09-12 10 views
4

제목이 더 나은 이름이 없기 때문에 자신을 명확하게 설명 할 수 있을지 잘 모르겠습니다. 색인을 통해 "데이터 유형"에 액세스하는 방법을 찾고 있지만 컴파일러가 배열에 보관하도록 강제하지는 않습니다. 이 문제는 SSE/AVX 내장 함수를 기반으로 한 저수준 코드 작성시 발생합니다. vector1vector2가 정의되어 있는지임시/"주소 지정 불가능"고정 크기 배열?

inline void load(__m512 *vector, const float *in) 
{ 
    for(int i=0; i<24; i++) 
     vector[i] = _mm512_load_ps((in + i*SIMD_WIDTH)); 
} 
// similarely: inline add(...) and inline store(...) 

void add(float *in_out, const float *in) 
{ 
    __m512 vector1[24]; 
    __m512 vector2[24]; 

    load(vector1, in_out); 
    load(vector2, in); 
    add(vector1, vector2); 
    store(in_out, vector1); 
} 

사실 :

프로그래밍의 용이성을 위해 나는 "등록"(데이터 유형 __m512)를 통해 고정 길이로, 다음과 같이 코드를 작성하는 루프를 싶습니다 배열은 컴파일러 (내 경우에는 icc)에 문제가있는 것 같습니다 : "주소 지정 가능"하게 만들고, 스택에 보관하므로 많은 필요없는및 store 명령어를 생성합니다. 지금까지 나는 포인터 연산을 vector1 또는 vector2으로 허용하는 것을 이해합니다.

나는 모든 것을 유지하기 위해 컴파일러에서 만 레지스터에 만 남기고 싶습니다. 이 입니다. __m512 배열을 사용하지 않고 코드를 작성하면 개가 각각__m512 개이며 컴파일러가 더 나은 코드를 생성합니다.

솔루션 :

내가 (실패)가 (I 스위치 문 바랐다 및 다른 모든 밖으로 최적화 한 것) 다음과 같은 클래스를 사용하여 시도

:

class Vector 
{ 
    inline __m512 & operator[](int i) 
    { 
     switch(i) 
      case 0: return component0; 
      // ... 
      case 23: return component23; 
    } 

    __m512 component0; 
    // ... 
    __m512 component23; 
}; 

나는 또한 고려했다 매크로가 있지만 좋은 해결책을 찾을 수 없습니다.

제안 사항? 여기 아래에 대답, 내가 뭘 원하는지에 대한 자세한 예에서 코멘트 다음

감사합니다,
사이먼

(이 비록 여전히 단순화) :

inline void project(__m512 *projected_vector, __m512 *vector) 
{ 
    for(int i=0; i<3; i++) 
     projected_vector[i] = _mm512_add_ps(vector[i], vector[i+3]); 
} 

inline void matrix_multiply(__m512 *out, const float *matrix, __m512 *in) 
{ 
    for(int i=0; i<3; i++) 
    { 
     out[i] = _mm512_mul_ps( matrix[3*i+0], in[0]); 
     out[i] = _mm512_fmadd_ps(matrix[3*i+1], in[1], out[i]); 
     out[i] = _mm512_fmadd_ps(matrix[3*i+2], in[2], out[i]); 
    } 
} 

inline void reconstruct(__m512 *vector, __m512 *projected_vector) 
{ 
    for(int i=0; i<3; i++) 
     vector[i] = _mm512_add_ps(vector[i], projected_vector[i]); 
    for(int i=0; i<3; i++) 
     vector[i+3] = _mm512_sub_ps(vector[i], projected_vector[i]); 
} 

inline void hopping_term(float *in_out, const float *matrix_3x3, const float *in) 
{ 
    __m512 vector_in[6]; 
    __m512 vector_out[6]; 
    __m512 half_vector1[3]; 
    __m512 half_vector2[3]; 

    load(vector_in, in); 
    project(half_vector1, vector_in); 
    matrix_multiply(half_vector2, matrix_3x3, half_vector1); 
    load(vector_out, in_out); 
    reconstruct(vector_out, half_vector2); 
    store(in_out, vector_out); 
} 
+0

매크로 경로를 가고 싶어하고, 모든 부두 자신을하지 않으려면, P99 유용 뭔가를 할 수 있습니다 : http://p99.gforge.inria.fr/p99-html/ – ninjalj

+0

SSE/AVX에 대해서는 아무 것도 모르지만 프록시가있는 사용자 정의 벡터가 유용 할 것 같습니다. –

+0

@ Mooing Duck : 더 자세히 설명해 주시겠습니까? '클래스 벡터'가 '__m512 component0 ... component23'에 대한 프록시 같은 질문에서 가능한 해결책으로 주어지지 않았습니까? 내 접근 방식은 원하는 효과를 가져 오지 않았습니다. 무엇이 변경됩니까? – Simon

답변

1

내 자신의 질문에 대한 답변을 제공하려면 : 나는 템플릿과 매크로의 조합을 기반으로 솔루션을 생각해 냈습니다.

  • struct 템플릿 함수가 벡터의 인덱스 템플릿 매개 변수로 전달 된 struct
  • 요소를 액세스하는 데 사용되는 __m512 변수
  • 의 "벡터"를 보유하고, 따라서 컴파일러로 관리 액세스 기능에 스위치 문을 최적화 할
  • 우리는 템플릿 매개 변수를 통해 C-루프를 사용할 수 없습니다, 그래서 가변 인자 매크로 루프
을 모방하는 데 사용됩니다3210

예 :

struct RegisterVector 
{ 
    __m512 c0; 
    __m512 c1; 
    __m512 c2; 
    __m512 c3; 
    __m512 c4; 
    __m512 c5; 
}; 

template <int vector_index> __m512 &element(RegisterVector &vector) 
{ 
    switch(vector_index) 
    { 
     case 0: return vector.c0; 
     case 1: return vector.c1; 
     case 2: return vector.c2; 
     case 3: return vector.c3; 
     case 4: return vector.c4; 
     case 5: return vector.c5; 
    } 
} 

#define LOOP3(loop_variable, start, ...) \ 
    do { \ 
    { const int loop_variable = start + 0; __VA_ARGS__; } \ 
    { const int loop_variable = start + 1; __VA_ARGS__; } \ 
    { const int loop_variable = start + 2; __VA_ARGS__; } \ 
    } while(0) 

// simple usage example 
LOOP3(c, 0, _mm512_add_ps(element<2*c+0>(vector1), element<2*c+1>(vector2))); 

// more complex example: more than one instruction in the loop, cmul and cfmadd itself are inline functions for a complex mul or fmadd 
LOOP3(r, 0, 
    cmul (su3[2*(3*0+r)+0], su3[2*(3*0+r)+1], element<2*0+0>(in), element<2*0+1>(in), element<2*r+0>(out), element<2*r+1>(out)); 
    cfmadd(su3[2*(3*1+r)+0], su3[2*(3*1+r)+1], element<2*1+0>(in), element<2*1+1>(in), element<2*r+0>(out), element<2*r+1>(out)); 
    cfmadd(su3[2*(3*2+r)+0], su3[2*(3*2+r)+1], element<2*2+0>(in), element<2*2+1>(in), element<2*r+0>(out), element<2*r+1>(out))); 

당신이 평소와 같이 템플릿 인수에 정수 연산을 수행 할 수 있습니다 볼 수 있으며, 루프 인덱스가 다른 배열에 액세스하는 데 사용될 수있다.

LOOP 매크로는 여전히 개선 될 수 있지만 제 목적으로는 충분합니다.

사이먼

+0

멋지다! 나는 당신을 위해 일하는 해결책을 찾은 것을 기쁘게 생각합니다. 나는 C++을 잘 못합니다. 매일 더 멀리 C++에서 멀어지고 C에 더 가깝게 이동합니다. NASM으로 어셈블리 코드를 작성하기 시작했습니다. 덧붙여, Intel XeonPhi를 사용하고 있습니까? AVX512에 어떻게 접근 할 수 있습니까? –

+0

예, 제온 파이 용입니다. 내 코드에서 볼 수있는 내장 함수는 제온 파이의 코드입니다. 그것들은 AVX512와 동일하지는 않지만 아주 가깝고 종종 동일하게 보입니다. 여기를 참조하십시오 : https://software.intel.com/sites/products/documentation/doclib/iss/2013/compiler/cpp-lin/index.htm#GUID-FC13EE09-8555-414B-8FF2-D7D66CD3975C.htm – Simon

1

내가 그랬어 아주 최근에 템플릿 메타 프로그래밍을 사용하여 이와 비슷한 것입니다. 다음 코드에서는 _mm256_mm512으로 바꾸고 SIMD_WIDTH16으로 변경해야한다고 생각합니다. 루프를 24 번 실행해야합니다. 내가 g++ -mavx -O3 -S -masm=intel 컴파일 할 때 나는이 방법을 사용했다

vmovaps ymm0, YMMWORD PTR [rdi] 
    vaddps ymm0, ymm0, YMMWORD PTR [rsi] 
    vmovaps YMMWORD PTR [rdx], ymm0 
    vmovaps ymm0, YMMWORD PTR [rdi+32] 
    vaddps ymm0, ymm0, YMMWORD PTR [rsi+32] 
    vmovaps YMMWORD PTR [rdx+32], ymm0 
    vmovaps ymm0, YMMWORD PTR [rdi+64] 
    vaddps ymm0, ymm0, YMMWORD PTR [rsi+64] 
    vmovaps YMMWORD PTR [rdx+64], ymm0 
    vmovaps ymm0, YMMWORD PTR [rdi+96] 
    vaddps ymm0, ymm0, YMMWORD PTR [rsi+96] 
    vmovaps YMMWORD PTR [rdx+96], ymm0 
    vmovaps ymm0, YMMWORD PTR [rdi+128] 
    vaddps ymm0, ymm0, YMMWORD PTR [rsi+128] 
    vmovaps YMMWORD PTR [rdx+128], ymm0 
    ... 
    vmovaps ymm0, YMMWORD PTR [rdi+736] 
    vaddps ymm0, ymm0, YMMWORD PTR [rsi+736] 
    vmovaps YMMWORD PTR [rdx+736], ymm0 

처럼 한 번에 4 개 마이크로 작전을 통해 (더 사용 융합)를 추진하기 위해 노력하고있어 어디

#include <x86intrin.h> 

#define SIMD_WIDTH 8 
#define LEN 24*SIMD_WIDTH 

template<int START, int N> 
struct Repeat { 
    static void add (float * x, float * y, float * z) { 
     _mm256_store_ps(&z[START],_mm256_add_ps(_mm256_load_ps(&x[START]) ,_mm256_load_ps(&y[START]))); 
     Repeat<START+SIMD_WIDTH, N>::add(x,y,z); 
    } 
}; 

template<int N> 
    struct Repeat<LEN, N> { 
    static void add (float * x, float * y, float * z) {} 
}; 


void sum_unroll(float *x, float *y, float *z, const int n) { 
    Repeat<0,LEN>::add(x,y,z); 
} 

어셈블리가 성공적으로 최근에 보인다. 예를 들어, 하나의 클럭 사이클에서 두 개의로드, 하나의 저장소 및 하나의 두 FMA를 수행 할 수 있습니다. 오류가 없는지 확인하기 위해 결과를 확인했습니다. 이 작업을 수행하는 몇 가지 테스트에서 효율성을 약간 높일 수있었습니다.

편집 : 여기 OP를 업데이트 한 질문을 기반으로 한 해결책이 있습니다. 나는 패스에서 SIMD 변수에 대한 배열을 사용하는 데 문제가 있었으므로 일반적으로 배열과 함께 사용하지 않습니다.또한 레지스터 이름 변경으로 인해 많은 SIMD 레지스터를 사용할 필요가 거의 없습니다 (가장 많이 사용한 레지스터는 11 개라고 생각합니다). 이 예에서는 5 개만 필요합니다.

#include <x86intrin.h> 

#define SIMD_WIDTH 8 

static inline __m256 load(const float *in) { 
    return _mm256_loadu_ps(in); 
} 

inline void store(float *out, __m256 const &vector) { 
    _mm256_storeu_ps(out, vector); 
} 

inline __m256 project(__m256 const &a, __m256 const &b) { 
    return _mm256_add_ps(a, b); 
} 

inline void reconstruct(__m256 &vector1, __m256 &vector2, __m256 &projected_vector) { 
    vector1 = _mm256_add_ps(vector1, projected_vector); 
    vector2 = _mm256_sub_ps(vector1, projected_vector); 
} 

class So { 
public: 
    __m256 half_vector[3]; 
    So(const float *in) { 
     for(int i=0; i<3; i++) 
      half_vector[i] = project(load(&in[i*SIMD_WIDTH]), load(&in[(3+i)*SIMD_WIDTH])); 
    } 

    __m256 matrix_multiply(const float *matrix) { 
     __m256 out; 
     out = _mm256_mul_ps(_mm256_loadu_ps(&matrix[0]), half_vector[0]); 
     out = _mm256_fmadd_ps(_mm256_loadu_ps(&matrix[1]), half_vector[1], out); 
     out = _mm256_fmadd_ps(_mm256_loadu_ps(&matrix[2]), half_vector[2], out); 
     return out; 
    } 
}; 

void hopping_term(float *in_out, const float *matrix_3x3, const float *in) 
{ 

    So so(in); 
    for(int i=0; i<3; i++) { 
     __m256 vector_out1, vector_out2; 
     __m256 half_vector2 = so.matrix_multiply(&matrix_3x3[3*i]); 
     vector_out1 = load(&in_out[i*SIMD_WIDTH]); 
     reconstruct(vector_out1, vector_out2, half_vector2); 
     store(&in_out[(0+i)*SIMD_WIDTH], vector_out1); 
     store(&in_out[(3+i)*SIMD_WIDTH], vector_out2); 
    } 
} 

이것은 5 개의 AVX 레지스터 만 사용합니다. 여기 어셈블리입니다

vmovups ymm3, YMMWORD PTR [rdx] 
    vmovups ymm2, YMMWORD PTR [rdx+32] 
    vaddps ymm3, ymm3, YMMWORD PTR [rdx+96] 
    vmovups ymm0, YMMWORD PTR [rdx+64] 
    vaddps ymm2, ymm2, YMMWORD PTR [rdx+128] 
    vaddps ymm0, ymm0, YMMWORD PTR [rdx+160] 
    vmulps ymm1, ymm3, YMMWORD PTR [rsi] 
    vfmadd231ps  ymm1, ymm2, YMMWORD PTR [rsi+4] 
    vfmadd231ps  ymm1, ymm0, YMMWORD PTR [rsi+8] 
    vaddps ymm4, ymm1, YMMWORD PTR [rdi] 
    vsubps ymm1, ymm4, ymm1 
    vmovups YMMWORD PTR [rdi], ymm4 
    vmovups YMMWORD PTR [rdi+96], ymm1 
    vmulps ymm1, ymm3, YMMWORD PTR [rsi+12] 
    vfmadd231ps  ymm1, ymm2, YMMWORD PTR [rsi+16] 
    vfmadd231ps  ymm1, ymm0, YMMWORD PTR [rsi+20] 
    vaddps ymm4, ymm1, YMMWORD PTR [rdi+32] 
    vsubps ymm1, ymm4, ymm1 
    vmovups YMMWORD PTR [rdi+32], ymm4 
    vmovups YMMWORD PTR [rdi+128], ymm1 
    vmulps ymm3, ymm3, YMMWORD PTR [rsi+24] 
    vfmadd132ps  ymm2, ymm3, YMMWORD PTR [rsi+28] 
    vfmadd132ps  ymm0, ymm2, YMMWORD PTR [rsi+32] 
    vaddps ymm1, ymm0, YMMWORD PTR [rdi+64] 
    vsubps ymm0, ymm1, ymm0 
    vmovups YMMWORD PTR [rdi+64], ymm1 
    vmovups YMMWORD PTR [rdi+160], ymm0 
+0

이것이 내가 원하는 것을하는 것이 확실하지 않습니다. 어쩌면 예제의 간결함을 잃어 버릴 수도 있습니다. 내가 이해하는 한 기본적으로 루프를 작성하는 것을 피하기 위해 재귀를 사용합니다. 즉, 언 롤링을 수행하는 것입니다. 그러나 언 롤링은 내 코드에서 문제가되지 않습니다. 컴파일러는이를 잘 수행합니다. 보다 복잡한 예제에서 솔루션이 어떻게 작동합니까? 단순한 "추가"이상의 작업, 즉 벡터로드, 3-5 명령어 수행, 벡터 저장 등의 작업을 수행하는 방법은 무엇입니까? 필자의 경우 3-5 명령어는 벡터 요소를 혼합 할 수 있습니다. – Simon

+0

@Simon,이 기술은 작업이 독립적 인 경우 잘 작동합니다. 필자는 독립적 인 한 3-5 가지 더 많은 계산 지침이 차이를 만들 것이라고 생각하지 않습니다. 요소를 혼합하거나 축소하는 경우 템플리트 메타 프로그래밍을 사용하는 방법을 잘 모르겠습니다. 현재 나는 아직도 그것을 "손으로"한다. –

+0

@ 사이먼 BTW GCC의'-funroll-loops'는 8 번만 풀렸다. 나는 더 원했다. 그래서 내가 이런 식으로 한거야. GCC는 또한 너무 많은 레지스터를 사용했습니다. 레지스터 이름 변경으로 인해 위에서 언 롤링은 하나의 논리 레지스터 만 사용합니다. –