다른 사람들이 말했듯 이 C ++ 14의 의미가 없다면__restrict__
C99와 동일한 GCC 확장을 고려하십시오.restrict
.
C99
restrict
두 포인터가 겹치는 메모리 영역을 가리킬 수 없다고 말합니다. 가장 일반적인 사용법은 함수 인수입니다.
이것은 함수 호출 방법을 제한하지만 더 많은 컴파일 최적화를 허용합니다.
발신자가 다음을 따르지 않으면 restrict
계약을 정의되지 않은 동작입니다.
C99 N1256 초안 6.7.3 / 7 "형식 한정자"말한다 :
제한 규정 자 (레지스터 저장 클래스와 같은)의 의도 된 사용은 최적화를 촉진하는 것이며, 규정을 준수하는 프로그램을 구성하는 모든 사전 처리 변환 단위에서 규정 자의 모든 인스턴스를 삭제해도 의미가 변경되지 않습니다 (즉, 관찰 가능한 동작).
그리고 6.7.3.1 "제한의 공식적 정의"는 세부 사항을 제공한다.
가능한 최적화
위키 백과의 예 입니다 매우 조명.
하나의 조립 지침을 저장 하는 방법을 명확하게 보여줍니다. .
제한없이 :
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 x may point to a (a aliased by x) thus
; the value of x will change when the value of a
; changes.
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" is no longer needed.
load R2 ← *b
add R2 += R1
set R2 → *b
GCC가 실제로합니까?
g++
4.8 리눅스 x86-64 :
g++ -g -std=gnu++98 -O0 -c main.cpp
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 가지 명령어와 3 개의 명령어.
배열
지금까지 단일 명령 절감 효과가 있지만, 포인터가 반복되는 배열, 일반적인 사용 사례를 나타내는 경우 supercat 과 michael이 언급 한 것처럼 많은 명령을 저장할 수 있습니다 .
예를 들면 다음과 같습니다.
void f(char *restrict p1, char *restrict p2, size_t size) {
for (size_t i = 0; i < size; i++) {
p1[i] = 4;
p2[i] = 9;
}
}
때문에 restrict
스마트 컴파일러 (또는 사람)는 다음과 같이 최적화 할 수 있습니다.
memset(p1, 4, size);
memset(p2, 9, size);
glibc와 같은 괜찮은 libc 구현에서 어셈블리 최적화 될 수 있으므로 잠재적으로 훨씬 효율적인 방법은 무엇입니까 성능 측면에서 std :: memcpy () 또는 std :: copy ()를 사용하는 것이 더 낫습니까? 가능성과 SIMD 명령어 .
제한 없이는이 최적화를 수행 할 수 없습니다. 예 :
char p1[4];
char *p2 = &p1[1];
f(p1, p2, 3);
그런 다음 for
버전은 다음을 만듭니다.
p1 == {4, 4, 4, 9}
memset
버전이 만드는 동안 :
p1 == {4, 9, 9, 9}
GCC가 실제로합니까?
GCC 5.2.1. 리눅스 x86-64 우분투 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 개의 반복 너비 루프 언 롤링 :-)
나는 그것들을 벤치마킹 할 인내가 없었지만 제한 버전이 더 빠를 것이라고 믿습니다.
엄격한 앨리어싱 규칙
restrict
키워드는 호환 가능한 유형의 포인터 (예 : 두에 영향을 미치는 int*
엄격한 앨리어싱 규칙이 호환되지 않는 유형의 별명을하는 것은 기본적으로 정의되지 않은 동작이라고 말한다 때문에), 그리고 컴파일러는 가정 할 수 있도록 멀리 일어날 최적화하지 않습니다.
참조 : 엄격한 앨리어싱 규칙은 무엇입니까?
참조 용으로 작동합니까?
GCC 문서에 따르면 https://gcc.gnu.org/onlinedocs/gcc-5.1.0/gcc/Restricted-Pointers.html 구문 은 다음 과 같습니다.
int &__restrict__ rref
this
멤버 함수 버전도 있습니다 :
void T::fn () __restrict__
restrict
c99 키워드입니다. 네, Rpbert S. Barnes는 대부분의 컴파일러가 지원한다는 것을 알고__restrict__
있습니다. 이중 밑줄이있는 것은 정의에 따라 구현에 따라 다르며 C ++ 이 아니라 컴파일러 특정 버전입니다.