我总是不确定,在C++中,restrict关键字是什么意思?
是否意味着赋予函数的两个或更多指针不重叠?还有什么意思?
在他的论文" 内存优化"中,Christer Ericson说,虽然restrict
它还不是C++标准的一部分,但它得到了许多编译器的支持,他建议在可用时使用它:
限制关键字
!1999年ANSI/ISO C标准的新成员
!尚未使用C++标准,但许多C++编译器都支持它
!只有提示,所以可能什么都不做,仍然符合要求
限制合格的指针(或参考)......
!...基本上是对编译器的承诺,对于指针的范围,指针的目标只能通过该指针(以及从中复制的指针)访问.
在支持它的C++编译器中,它应该与C中的行为相同.
有关详细信息,请参阅此SO帖子: C99'conctrict'关键字的实际用法?
花半个小时浏览Ericson的论文,这很有趣,值得花时间.
编辑
我还发现IBM的AIX C/C++编译器支持该__restrict__
关键字.
g ++似乎也支持这个,因为下面的程序在g ++上完全编译:
#includeint foo(int * __restrict__ a, int * __restrict__ b) { return *a + *b; } int main(void) { int a = 1, b = 1, c; c = foo(&a, &b); printf("c == %d\n", c); return 0; }
我还发现了一篇关于使用的好文章restrict
:
揭开限制关键词的神秘面纱
EDIT2
我碰到了一篇专门讨论在C++程序中使用restrict的文章:
加载命中存储和__restrict关键字
此外,Microsoft Visual C++ 还支持该__restrict
关键字.
正如其他人所说,如果C++ 14中没有任何意义,那么让我们考虑一下__restrict__
与C99相同的GCC扩展restrict
.
C99
restrict
说两个指针不能指向重叠的内存区域.最常见的用法是函数参数.
这限制了函数的调用方式,但允许更多的编译优化.
如果调用者不遵循restrict
合同,则定义未定义的行为.
该C99 N1256草案 6.7.3/7 "类型的限定"说:
restrict限定符(如寄存器存储类)的预期用途是促进优化,并且从构成符合程序的所有预处理转换单元中删除限定符的所有实例不会改变其含义(即,可观察行为).
和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条指令vs 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 4283f5: 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 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 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*
),因为严格的别名规则表明,默认情况下,别名不兼容类型是未定义的行为,因此编译器可以假设它不会发生并优化掉.
请参阅:什么是严格别名规则?
它适用于参考?
根据海湾合作委员会的文件,它确实:https://gcc.gnu.org/onlinedocs/gcc-5.1.0/gcc/Restricted-Pointers.html语法:
int &__restrict__ rref
甚至还有this
成员函数的版本:
void T::fn () __restrict__
没有.它被添加到C99标准中.
这是添加此关键字的原始提案.尽管如此指出,这是一个C99功能; 它与C++无关.
C++中没有这样的关键字.C++关键字列表可以在C++语言标准的2.11/1节中找到.restrict
是C语言的C99版本中的关键字,而不是C++中的关键字.