我一直不确定,C++ 中的 restrict 关键字是什么意思?
这是否意味着赋予函数的两个或多个指针不重叠?还有什么意思?
在他的论文Memory Optimization中,Christer Ericson 说虽然restrict
它还不是 C++ 标准的一部分,但许多编译器都支持它,他建议在可用时使用它:
限制关键字
!新 1999 ANSI/ISO C 标准
!尚不在 C++ 标准中,但被许多 C++ 编译器支持
!只是一个提示,所以可能什么都不做但仍然符合
限制限定的指针(或引用)...
!...基本上是对编译器的承诺,对于指针的范围,指针的目标将只能通过该指针(以及从它复制的指针)访问。
在支持它的 C++ 编译器中,它的行为可能与 C 中的相同。
有关详细信息,请参阅此 SO 帖子: C99 'restrict' 关键字的实际用法?
花半小时浏览一下 Ericson 的论文,很有趣,值得花时间。
编辑
我还发现 IBM 的AIX C/C++ 编译器支持__restrict__
关键字.
g++ 似乎也支持这一点,因为以下程序可以在 g++ 上干净地编译:
#include <stdio.h>
int 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
:
编辑2
我遇到了一篇专门讨论在 C++ 程序中使用限制的文章:
此外,Microsoft Visual C++也支持__restrict
关键字.
正如其他人所说,它从 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
海湾合作委员会真的做到了吗?
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
关键字仅影响兼容类型的指针(例如 two int*
),因为严格的别名规则表明别名不兼容的类型默认情况下是未定义的行为,因此编译器可以假定它不会发生并优化掉。
请参阅:什么是严格的别名规则?
它对参考有用吗?
根据 GCC 文档,它确实:https ://gcc.gnu.org/onlinedocs/gcc-5.1.0/gcc/Restricted-Pointers.html语法:
int &__restrict__ rref
甚至还有一个this
成员函数的版本:
void T::fn () __restrict__
没有什么。它被添加到 C99 标准中。
由于某些 C 库的头文件使用关键字,C++ 语言将不得不对此做一些事情.. 至少忽略关键字,因此我们不必将关键字#define 为空白宏来抑制关键字.
C++ 中没有这样的关键字。C++ 关键字列表可以在 C++ 语言标准的第 2.11/1 节中找到。restrict
是 C 语言 C99 版本中的关键字,而不是 C++ 中的关键字。
有了__restrict__
,compiler
可以进行复杂的优化,因为程序员已经保证__restrict__
修饰点指向绝对不会相互重叠的数据范围。pointers
这通常是这种情况,因此为了实现高性能目标,您最多可以在代码中添加一个__restrict__
装饰器pointers
。