문제

나는 문서와 질문/답변을 탐색하고 언급 된 것을 보았습니다. 나는 간단한 설명을 읽었습니다. 기본적으로 포인터가 다른 곳을 가리키는 데 사용되지 않을 것이라는 프로그래머의 약속이 될 것이라고 말합니다.

누구든지 실제로 사용 할 가치가있는 현실적인 사례를 제공 할 수 있습니까?

도움이 되었습니까?

해결책

restrict 포인터는 기본 객체에 액세스하는 유일한 것입니다. 포인터 별명의 가능성을 제거하여 컴파일러의 최적화를 향상시킬 수 있습니다.

예를 들어, 메모리에 숫자의 벡터를 곱할 수있는 특수 지침이있는 기계가 있다고 가정하고 다음과 같은 코드가 있다고 가정합니다.

void MultiplyArrays(int* dest, int* src1, int* src2, int n)
{
    for(int i = 0; i < n; i++)
    {
        dest[i] = src1[i]*src2[i];
    }
}

컴파일러는 IF를 올바르게 처리해야합니다 dest, src1, 그리고 src2 오버랩은 시작부터 끝까지 한 번에 하나의 곱셈을 수행해야한다는 것을 의미합니다. 가지고 있습니다 restrict, 컴파일러는 벡터 지침을 사용 하여이 코드를 최적화 할 수 있습니다.

Wikipedia에 입장이 있습니다 restrict, 또 다른 예와 함께 여기.

다른 팁

그만큼 Wikipedia 예제 ~이다 매우 조명.

그것은 어떻게 분명히 보여줍니다 하나의 어셈블리 명령을 저장할 수 있습니다.

제한없이 :

void f(int *a, int *b, int *x) {
  *a += *x;
  *b += *x;
}

의사 조립 :

load R1 ← *x    ; Load the value of x pointer
load R2 ← *a    ; Load the value of a pointer
add R2 += R1    ; Perform Addition
set R2 → *a     ; Update the value of a pointer
; Similarly for b, note that x is loaded twice,
; because a may be equal to x.
load R1 ← *x
load R2 ← *b
add R2 += R1
set R2 → *b

제한 :

void fr(int *restrict a, int *restrict b, int *restrict x);

의사 조립 :

load R1 ← *x
load R2 ← *a
add R2 += R1
set R2 → *a
; Note that x is not reloaded,
; because the compiler knows it is unchanged
; load R1 ← *x
load R2 ← *b
add R2 += R1
set R2 → *b

GCC가 실제로 그렇게합니까?

GCC 4.8 Linux x86-64 :

gcc -g -std=c99 -O0 -c main.c
objdump -S main.o

와 함께 -O0, 그들은 동일합니다.

와 함께 -O3:

void f(int *a, int *b, int *x) {
    *a += *x;
   0:   8b 02                   mov    (%rdx),%eax
   2:   01 07                   add    %eax,(%rdi)
    *b += *x;
   4:   8b 02                   mov    (%rdx),%eax
   6:   01 06                   add    %eax,(%rsi)  

void fr(int *restrict a, int *restrict b, int *restrict x) {
    *a += *x;
  10:   8b 02                   mov    (%rdx),%eax
  12:   01 07                   add    %eax,(%rdi)
    *b += *x;
  14:   01 06                   add    %eax,(%rsi) 

초기화되지 않은 경우 전화 컨벤션 이다:

  • rdi = 첫 번째 매개 변수
  • rsi = 두 번째 매개 변수
  • rdx = 세 번째 매개 변수

GCC 출력은 Wiki 기사보다 훨씬 명확했습니다. 4 지침 vs 3 지침.

배열

지금까지 우리는 단일 명령 저축이 있지만, 포인터가 반복 될 배열을 표현하면 공통 사용 사례를 사용하면 다음과 같이 언급했듯이 많은 지침을 저장할 수 있습니다. 슈퍼 캣.

예를 들어 고려하십시오 :

void f(char *restrict p1, char *restrict p2) {
    for (int i = 0; i < 50; i++) {
        p1[i] = 4;
        p2[i] = 9;
    }
}

때문에 restrict, 스마트 컴파일러 (또는 사람)는 다음을 최적화 할 수 있습니다.

memset(p1, 4, 50);
memset(p2, 9, 50);

GLIBC와 같은 적절한 LIBC 구현에 최적화 될 수 있으므로 잠재적으로 훨씬 더 효율적일 수 있습니다. 성능에 따라 std :: memcpy () 또는 std :: copy ()를 사용하는 것이 더 낫습니까?

GCC가 실제로 그렇게합니까?

GCC 5.2.1.Linux X86-64 Ubuntu 15.10 :

gcc -g -std=c99 -O0 -c main.c
objdump -dr main.o

와 함께 -O0, 둘 다 동일합니다.

와 함께 -O3:

  • 제한 :

    3f0:   48 85 d2                test   %rdx,%rdx
    3f3:   74 33                   je     428 <fr+0x38>
    3f5:   55                      push   %rbp
    3f6:   53                      push   %rbx
    3f7:   48 89 f5                mov    %rsi,%rbp
    3fa:   be 04 00 00 00          mov    $0x4,%esi
    3ff:   48 89 d3                mov    %rdx,%rbx
    402:   48 83 ec 08             sub    $0x8,%rsp
    406:   e8 00 00 00 00          callq  40b <fr+0x1b>
                            407: R_X86_64_PC32      memset-0x4
    40b:   48 83 c4 08             add    $0x8,%rsp
    40f:   48 89 da                mov    %rbx,%rdx
    412:   48 89 ef                mov    %rbp,%rdi
    415:   5b                      pop    %rbx
    416:   5d                      pop    %rbp
    417:   be 09 00 00 00          mov    $0x9,%esi
    41c:   e9 00 00 00 00          jmpq   421 <fr+0x31>
                            41d: R_X86_64_PC32      memset-0x4
    421:   0f 1f 80 00 00 00 00    nopl   0x0(%rax)
    428:   f3 c3                   repz retq
    

    memset 예상대로 호출.

  • 제한없이 : stdlib 호출 없음, 16 반복 너비 만 루프 unprolling 나는 여기서 재생산하지 않는다 :-)

나는 그들을 벤치마킹 할 인내심이 없었지만 제한 버전이 더 빠를 것이라고 생각합니다.

C99

완전성을위한 표준을 살펴 보겠습니다.

restrict 두 포인터는 메모리 영역을 겹치는 것을 가리킬 수 없다고 말합니다. 가장 일반적인 사용법은 기능 인수입니다.

이는 기능을 호출 할 수있는 방법을 제한하지만 더 많은 컴파일 타임 최적화를 허용합니다.

발신자가 따르지 않는 경우 restrict 계약, 정의되지 않은 행동.

그만큼 C99 N1256 초안 6.7.3/7 "유형 예정자"는 말합니다.

레지스터 스토리지 클래스와 같은 제한 예선의 의도 된 사용은 최적화를 촉진하고, 순응 프로그램을 구성하는 모든 사전 처리 번역 장치에서 예선의 모든 인스턴스를 삭제하는 것이 의미를 바꾸지는 않습니다 (즉, 관찰 가능한 행동).

6.7.3.1 "제한의 공식 정의"는 Gory 세부 사항을 제공합니다.

엄격한 별칭 규칙

그만큼 restrict 키워드는 호환 유형의 포인터에만 영향을 미칩니다 (예 : 두 가지 int*) 엄격한 별칭 규칙에 따르면 호환되지 않는 유형의 별칭은 기본적으로 정의되지 않은 동작이므로 컴파일러는 발생하지 않고 최적화 할 수 있다고 가정 할 수 있습니다.

보다: 엄격한 별칭 규칙은 무엇입니까?

또한보십시오

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top