就像其他人说的那样,如果从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 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
海湾合作委员会真的做到了吗?
g++
4.8 Linux 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);
哪个可能更有效,因为它可以在像样的libc实现(例如glibc)上进行装配优化,就性能而言,使用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 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个迭代宽的循环展开,我不想在这里重现:-)
我没有耐心对它们进行基准测试,但我相信限制版本会更快。
严格的别名规则
该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 ++,而是编译器的特定版本。