sourcecode

GCC 앨리어싱 확인/제한 포인터 포함

codebag 2023. 10. 25. 23:19
반응형

GCC 앨리어싱 확인/제한 포인터 포함

다음 두 가지 토막글을 생각해 보십시오.

#define ALIGN_BYTES 32
#define ASSUME_ALIGNED(x) x = __builtin_assume_aligned(x, ALIGN_BYTES)

void fn0(const float *restrict a0, const float *restrict a1,
         float *restrict b, int n)
{
    ASSUME_ALIGNED(a0); ASSUME_ALIGNED(a1); ASSUME_ALIGNED(b);

    for (int i = 0; i < n; ++i)
        b[i] = a0[i] + a1[i];
}

void fn1(const float *restrict *restrict a, float *restrict b, int n)
{
    ASSUME_ALIGNED(a[0]); ASSUME_ALIGNED(a[1]); ASSUME_ALIGNED(b);

    for (int i = 0; i < n; ++i)
        b[i] = a[0][i] + a[1][i];
}

함수를 컴파일하면 다음과 같이 됩니다.gcc-4.7.2 -Ofast -march=native -std=c99 -ftree-vectorizer-verbose=5 -S test.c -Wall나는 GCC가 두 번째 함수에 대해 앨리어싱 체크를 삽입한다는 것을 발견했습니다.

이를 방지하려면 어떻게 해야 하나요?fn1에 대한 것과 같습니다.fn0? (파라미터의 수가 3개에서 30개로 증가할 때 인수 통과 접근법(fn0)가 번거로워지고 에일리어싱 체크의 수가 증가합니다.fn1어프로치가 우스꽝스러워집니다.)

어셈블리(x86-64, AVX 지원 칩); .LFB10의 앨리어싱 크루프트

fn0:
.LFB9:
    .cfi_startproc
    testl   %ecx, %ecx
    jle .L1
    movl    %ecx, %r10d
    shrl    $3, %r10d
    leal    0(,%r10,8), %r9d
    testl   %r9d, %r9d
    je  .L8
    cmpl    $7, %ecx
    jbe .L8
    xorl    %eax, %eax
    xorl    %r8d, %r8d
    .p2align 4,,10
    .p2align 3
.L4:
    vmovaps (%rsi,%rax), %ymm0
    addl    $1, %r8d
    vaddps  (%rdi,%rax), %ymm0, %ymm0
    vmovaps %ymm0, (%rdx,%rax)
    addq    $32, %rax
    cmpl    %r8d, %r10d
    ja  .L4
    cmpl    %r9d, %ecx
    je  .L1
.L3:
    movslq  %r9d, %rax
    salq    $2, %rax
    addq    %rax, %rdi
    addq    %rax, %rsi
    addq    %rax, %rdx
    xorl    %eax, %eax
    .p2align 4,,10
    .p2align 3
.L6:
    vmovss  (%rsi,%rax,4), %xmm0
    vaddss  (%rdi,%rax,4), %xmm0, %xmm0
    vmovss  %xmm0, (%rdx,%rax,4)
    addq    $1, %rax
    leal    (%r9,%rax), %r8d
    cmpl    %r8d, %ecx
    jg  .L6
.L1:
    vzeroupper
    ret
.L8:
    xorl    %r9d, %r9d
    jmp .L3
    .cfi_endproc
.LFE9:
    .size   fn0, .-fn0
    .p2align 4,,15
    .globl  fn1
    .type   fn1, @function
fn1:
.LFB10:
    .cfi_startproc
    testq   %rdx, %rdx
    movq    (%rdi), %r8
    movq    8(%rdi), %r9
    je  .L12
    leaq    32(%rsi), %rdi
    movq    %rdx, %r10
    leaq    32(%r8), %r11
    shrq    $3, %r10
    cmpq    %rdi, %r8
    leaq    0(,%r10,8), %rax
    setae   %cl
    cmpq    %r11, %rsi
    setae   %r11b
    orl %r11d, %ecx
    cmpq    %rdi, %r9
    leaq    32(%r9), %r11
    setae   %dil
    cmpq    %r11, %rsi
    setae   %r11b
    orl %r11d, %edi
    andl    %edi, %ecx
    cmpq    $7, %rdx
    seta    %dil
    testb   %dil, %cl
    je  .L19
    testq   %rax, %rax
    je  .L19
    xorl    %ecx, %ecx
    xorl    %edi, %edi
    .p2align 4,,10
    .p2align 3
.L15:
    vmovaps (%r9,%rcx), %ymm0
    addq    $1, %rdi
    vaddps  (%r8,%rcx), %ymm0, %ymm0
    vmovaps %ymm0, (%rsi,%rcx)
    addq    $32, %rcx
    cmpq    %rdi, %r10
    ja  .L15
    cmpq    %rax, %rdx
    je  .L12
    .p2align 4,,10
    .p2align 3
.L20:
    vmovss  (%r9,%rax,4), %xmm0
    vaddss  (%r8,%rax,4), %xmm0, %xmm0
    vmovss  %xmm0, (%rsi,%rax,4)
    addq    $1, %rax
    cmpq    %rax, %rdx
    ja  .L20
.L12:
    vzeroupper
    ret
.L19:
    xorl    %eax, %eax
    jmp .L20
    .cfi_endproc

컴파일러에게 앨리어싱 검사를 중지하도록 지시하는 방법이 있습니다.

줄을 추가하십시오.

#pragma GCC ivdep

벡터화하고 싶은 루프의 바로 앞에, 더 많은 정보가 필요하다면 읽어주시기 바랍니다:

https://gcc.gnu.org/onlinedocs/gcc-4.9.2/gcc/Loop-Specific-Pragmas.html

이게 도움이 될까요?

void fn1(const float **restrict a, float *restrict b, int n)
{
    const float * restrict a0 = a[0];
    const float * restrict a1 = a[1];

    ASSUME_ALIGNED(a0); ASSUME_ALIGNED(a1); ASSUME_ALIGNED(b);

    for (int i = 0; i < n; ++i)
        b[i] = a0[i] + a1[i];
}

편집: 두 번째 시도 :).http://locklessinc.com/articles/vectorize/ 의 정보와 함께

gcc --fast-math ...

그럼 깃발은?

-fno-strict-aliasing

?

제가 이해한 대로 이 체크를 어떻게 끄는지 알고 싶으신가요?그게 전부라면, gcc 명령줄에 대한 이 매개 변수가 당신에게 도움이 될 것입니다.

편집:

귀하의 의견 외에, const type 제한 포인터를 사용하는 것은 금지되어 있지 않습니까?

이는 ISO/IEC 9899 (6.7.3.1 제한의 공식적인 정의)에서 나온 것입니다.

1.

D를 T형에 대한 제한 자격 포인터로서 객체 P를 지정하는 수단을 제공하는 일반 식별자의 선언이라고 가정합니다.

4.

B를 실행할 때마다 L을 P를 기준으로 &L을 갖는 임의의 l 값이라 하자. L을 사용하여 자신이 지정한 개체 X의 값에 접근하고 X도 (어떤 방법으로든) 수정된다면 다음과 같은 요구 사항이 적용됩니다.T는 일정한 자격이 없어야 합니다.X 값에 접근하기 위해 사용되는 다른 모든 l 값은 P를 기준으로 주소를 가져야 합니다.X를 수정하는 모든 접근은 이 하위 절의 목적을 위해 P를 수정하는 것도 고려해야 합니다.블록 B2와 관련된 다른 제한된 포인터 객체 P2를 기반으로 하는 포인터 식 E의 값이 P에 할당된 경우, B2의 실행은 B의 실행 전에 시작되거나 B2의 실행은 할당 전에 종료됩니다.이러한 요구 사항이 충족되지 않으면 동작이 정의되지 않습니다.

그리고 레지스터와 마찬가지로 훨씬 더 흥미로운 점은 다음과 같습니다.

6.

번역자는 제한을 사용할 때 어떤 또는 모든 앨리어싱의 의미를 무시할 수 있습니다.

따라서 gcc가 그렇게 하도록 강제하는 명령 파라미터를 찾을 수 없다면, 표준에서 그렇게 하는 옵션을 줄 필요가 없기 때문에 아마도 불가능할 것입니다.

GCC 4.7로 결과를 기계에 재현할 수 없기 때문에 미리 사과드립니다만, 두 가지 가능한 해결책이 있습니다.

  1. typedef를 사용하여 a를 작성합니다.* restrict * restrict적절히.LLVM 컴파일러를 개발하는 전 동료에 따르면, 이것은 다음과 같은 단 하나의 예외입니다.typedefC의 전처리기처럼 동작하며 원하는 안티 앨리어싱 동작을 허용하기 위해 존재합니다.

    저는 아래에서 이것을 시도해 보았지만 성공했는지 확신할 수 없습니다.저의 시도를 잘 확인해 주시기 바랍니다.

  2. C99 VLA(Variable Length Array)와 함께 제한 한정자를 사용하는 것에 대한 답변에 설명된 구문을 사용합니다.

    저는 아래에서 이것을 시도해 보았지만 성공했는지 확신할 수 없습니다.저의 시도를 잘 확인해 주시기 바랍니다.

여기 제가 실험을 수행할 때 사용한 코드가 있습니다만, 제 제안 중 어느 것이든 원하는 대로 작동하는지 단정적으로 판단하지 못했습니다.

#define ALIGN_BYTES 32
#define ASSUME_ALIGNED(x) x = __builtin_assume_aligned(x, ALIGN_BYTES)

void fn0(const float *restrict a0, const float *restrict a1,
         float *restrict b, int n)
{
    ASSUME_ALIGNED(a0); ASSUME_ALIGNED(a1); ASSUME_ALIGNED(b);

    for (int i = 0; i < n; ++i)
        b[i] = a0[i] + a1[i];
}

#if defined(ARRAY_RESTRICT)
void fn1(const float *restrict a[restrict], float * restrict b, int n)
#elif defined(TYPEDEF_SOLUTION)
typedef float * restrict frp;
void fn1(const frp *restrict a, float *restrict b, int n)
#else
void fn1(const float *restrict *restrict a, float *restrict b, int n)
#endif
{
    //ASSUME_ALIGNED(a[0]); ASSUME_ALIGNED(a[1]); ASSUME_ALIGNED(b);

    for (int i = 0; i < n; ++i)
        b[i] = a[0][i] + a[1][i];
}

제한을 사용하여 얻을 수 있는 거의 모든 성능상의 이점에는 다음과 같은 두 가지 사용 패턴 중 하나가 포함됩니다.

  1. 지정된 함수 인수에 직접 적용되는 제한 한정자(여기서 가리키는 것과 반대)

  2. 이니셜라이저가 있는 명명된 자동 개체에 직접 적용되는 제한 한정자입니다.

이 두 가지 맥락에서 한정자는 명명된 객체의 초기 값을 기반으로 포인터에 의해 액세스되는 저장소를 "보호"하고 이러한 보호의 용어는 객체가 초기화된 시점부터 수명이 끝날 때까지 확장된다는 것이 분명합니다.

제한 한정자가 다른 환경에서 사용되는 경우 의미론이 무엇인지 훨씬 명확하지 않습니다.Standard는 다른 유형이 어떻게 작동해야 하는지 지정하려고 하지만, 적용하려는 컴파일러에 대해서는 알지 못합니다.

예를 들면 다음과 같습니다.

extern int x,y;

int *xx = &x, *yy = &y;
int *restrict *restrict pp;

pp = &xx;
int *q = *pp;
*q = 1;
pp = &yy;
... other code

한다면q다음에는 사용되지 않습니다.*q=1;위와 같이 "restrict" 한정자가 켜져 있는 경우*pp경계를 계속함x그 후에도pp그 자체가 다음을 가리키도록 바뀝니다.yy. 위원회가 그러한 문제를 고려하여 합의에 도달했거나 컴파일러 작성자가 그러한 사례를 의미 있게 처리하려고 시도했다는 증거가 있습니까?

의미있는 처리.restrict한정자는 이렇게 설정된 "guarded 포인터 값"이 잘 정의된 수명을 가져야 합니다.위에서 설명한 두 가지 이상의 사례를 처리하기 위해서는 상당한 노력이 필요하지만 상대적으로 최소의 혜택을 제공할 수 있습니다.

예제 코드가 선언을 사용하도록 변경된 경우int *restrict q = *pp;, 다음의 가치는 분명할 것입니다.x다음의 범위 내에 있다면 "기타 코드"로 보호될 것입니다.q, 그러나 그것은 컴파일러가 외부 레벨을 인식했는지 여부와 관계없이 사실일 것입니다.restrict에 대한 예선전pp. 그렇다면 왜 그런 문제를 해결해야 합니까?

언급URL : https://stackoverflow.com/questions/15613670/gcc-aliasing-checks-w-restrict-pointers

반응형