GCC가

2017-04-27 12 views
23

여기 일부 std::array 사용시 GCC 6,7 최적화 실패 코드의 C 배열처럼 정렬 표준 : 배열을 최적화하기 위해 실패 생성 g++ -std=c++14 -O3 -march=haswell -S -DC_ARRAY와 상기 컴파일GCC가

#include <array> 

static constexpr size_t my_elements = 8; 

class Foo 
{ 
public: 
#ifdef C_ARRAY 
    typedef double Vec[my_elements] alignas(32); 
#else 
    typedef std::array<double, my_elements> Vec alignas(32); 
#endif 
    void fun1(const Vec&); 
    Vec v1{{}}; 
}; 

void Foo::fun1(const Vec& __restrict__ v2) 
{ 
    for (unsigned i = 0; i < my_elements; ++i) 
    { 
     v1[i] += v2[i]; 
    } 
} 

좋은 코드 :

vmovapd ymm0, YMMWORD PTR [rdi] 
    vaddpd ymm0, ymm0, YMMWORD PTR [rsi] 
    vmovapd YMMWORD PTR [rdi], ymm0 
    vmovapd ymm0, YMMWORD PTR [rdi+32] 
    vaddpd ymm0, ymm0, YMMWORD PTR [rsi+32] 
    vmovapd YMMWORD PTR [rdi+32], ymm0 
    vzeroupper 

이것은 기본적으로 256 비트 레지스터를 통해 한 번에 4 개의 double을 추가하는 두 번 풀지 않은 반복 작업입니다. 당신이 -DC_ARRAY없이 컴파일하는 경우, 당신은이로 시작하는 큰 혼란을 얻을 :

mov  rax, rdi 
    shr  rax, 3 
    neg  rax 
    and  eax, 3 
    je  .L7 

이 경우 생성 된 코드는 (대신 일반 C 배열의 std::array 사용)

입력의 정렬을 확인하는 것 array-- 32 바이트에 정렬 된 typedef에 지정되어 있어도 마찬가지입니다.

std::array의 내용이 std::array과 동일하게 정렬되어있는 것으로 GCC에서 이해하지 못하는 것 같습니다. 이는 C 배열 대신 std::array을 사용하면 런타임 비용이 발생하지 않는다는 가정을 깨뜨린 것입니다.

간단한 문제가 있습니까? 해결 방법은 없습니까? 지금까지 내가 못생긴 해킹 함께했다 :

void Foo::fun2(const Vec& __restrict__ v2) 
{ 
    typedef double V2 alignas(Foo::Vec); 
    const V2* v2a = static_cast<const V2*>(&v2[0]); 

    for (unsigned i = 0; i < my_elements; ++i) 
    { 
     v1[i] += v2a[i]; 
    } 
} 

이 또한주의 : my_elements 4 대신 8 인 경우, 문제가 발생하지 않습니다. Clang을 사용하면 문제가 발생하지 않습니다. , GCC는 C의 경우뿐만 아니라 표준 : 배열 케이스를 최적화하기 위해 관리 https://godbolt.org/g/IXIOst

+4

FWIW을, 그 소리는 alignas'이 아닌 형식 정의에 데이터 멤버에 있어야하지만,'Vec'을 변경하는 경우 '불평 'std :: array <...>'을 정렬 된 데이터 멤버로 가지고있는 중첩 클래스에'operator []'오버로드를 주면, clang은 이것을 최적화하도록 관리한다. GCC는 여전히 그렇지 않습니다. – hvd

+3

'std :: array'의 기본 배열은'std :: array'와 같은 정렬을 가지고 있습니까? –

+1

그리고'Vec'가'double data [my_elements] alignas (32);'를 가지고 있고, 커스텀'operator []'로 구현되어 있다면, GCC는 이것을 최적화 할 수 있습니다. 그 문제는'array :: operator []'가 정렬되지 않은'array :: _ M_elems' 멤버에서 오는 정렬되지 않은'double '을 반환한다는 것과 배열 된 배열의 일부라는 사실이 너무 멀었다는 것입니다 옵티 마이저가 그것을 볼 수 있도록. – hvd

답변

17

을 흥미롭게도, 당신이 (분명 휴대용하지 않은) v1._M_elems[i] += v2._M_elems[i];v1[i] += v2a[i];를 교체하는 경우 :

당신은 여기 살고 볼 수 있습니다 정렬.

가능한 해석 : gcc 덤프 (-fdump-tree-all-all)에서 C 배열의 경우 MEM[(struct FooD.25826 *)this_7(D) clique 1 base 0].v1D.25832[i_15]을, std :: array의 경우 MEM[(const value_typeD.25834 &)v2_7(D) clique 1 base 1][_1]을 볼 수 있습니다. 즉, 두 번째 경우에 gcc는 Foo 유형의 일부이며 이중 액세스 만 기억한다는 것을 잊어 버릴 수 있습니다.

이것은 결국 배열 액세스를보기 위해 통과해야하는 모든 인라인 함수에서 오는 추상화 패널티입니다. Clang은 여전히 ​​정렬을 제거한 후에도 벡터화를 잘 관리합니다. 이것은 clang이 정렬을 신경 쓰지 않고 벡터화하고 실제로는 정렬 된 주소가 필요없는 vmovupd과 같은 명령어를 사용한다는 것을 의미합니다.

Vec로 캐스팅 한 해킹은 컴파일러가 메모리 액세스를 처리 할 때 처리되는 유형이 정렬되도록하는 또 다른 방법입니다. 일반적인 std :: array :: operator []의 경우 메모리 액세스는 *this이 정렬된다는 단서가없는 std :: array의 멤버 함수 내에서 발생합니다.

GCC는 컴파일러가 정렬에 대해 알려 할 수있는 내장이 있습니다

const double*v2a=static_cast<const double*>(__builtin_assume_aligned(v2.data(),32)); 
+10

여기 GCC에보고했습니다 : https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80561 –

+3

버그 보고서를 제출해 주셔서 감사합니다 :-) –