restrict关键字在C ++中的含义是什么?
我总是不确定,C ++中的restrict关键词是什么意思?
这是否意味着给函数的两个或多个指针不重叠? 这是什么意思?
Christer Ericson在他的文章“ 内存优化” ( Memory Optimization)中说,虽然restrict
并不是C ++标准的一部分,但它被许多编译器支持,而且他build议在可用时使用它:
限制关键字
! 1999年新的ANSI / ISO C标准
! 目前还没有C ++标准,但是被许多C ++编译器支持
! 只有一个提示,所以可能什么都不做,仍然是一致的
限制合格的指针(或引用)…
! …基本上是对编译器的一个承诺,对于指针的作用域,指针的目标只能通过该指针(以及从中复制的指针)来访问。
在支持它的C ++编译器中,它可能应该和C中一样
看到这个SOpost的细节: 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
的好文章:
揭秘限制关键字
EDIT2
我遇到了一篇专门讨论在C ++程序中使用restrict的文章:
Load-hit-stores和__restrict关键字
另外,Microsoft Visual C ++ 也支持__restrict
关键字 。
正如其他人所说,如果没有C ++ 14的意思 ,那么让我们考虑一下__restrict__
GCC扩展,它和C99的restrict
。
C99
restrict
说,两个指针不能指向相同的内存位置。 最常见的用法是函数参数。
这限制了函数如何被调用,但允许更多的编译优化。
如果调用者不遵守restrict
合同,则会导致未定义的行为。
C99 N1256草案 6.7.3 / 7“types限定符”说:
限定限定符(比如寄存器存储类)的用途是促进优化,并且从组成合格程序的所有预处理翻译单元删除限定词的所有实例不会改变其含义(即,可观察到的行为)。
和6.7.3.1“限制的正式定义”给出了细节。
一个可能的优化
维基百科的例子 非常有启发性。
它清楚地显示了如何保存一条汇编指令 。
没有限制:
void f(int *a, int *b, int *x) { *a += *x; *b += *x; }
伪assembly:
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);
伪assembly:
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
=第三个参数
海湾合作委员会的输出比维基文章更清晰: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 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
关键字只影响兼容types的指针(例如两个int*
),因为严格的别名规则说默认的别名不兼容types是未定义的行为,所以编译器可以认为它不会发生并优化。
请参阅: 什么是严格的别名规则?
它是否适用于参考?
根据GCC文档: https : //gcc.gnu.org/onlinedocs/gcc-5.1.0/gcc/Restricted-Pointers.html语法:
int &__restrict__ rref
甚至有一个this
的成员函数的版本:
void T::fn () __restrict__
没有。 它被添加到C99标准。
这是添加此关键字的原始提案。 正如Dirkgently指出的,这是一个C99function; 它与C ++无关。
C ++中没有这样的关键字。 C ++关键字列表可以在C ++语言标准的2.11 / 1节find。 restrict
是C99版C语言中的关键字,而不是C ++中的关键字。
由于某些C库的头文件使用关键字,所以C ++语言必须做些什么…至less,忽略关键字,所以我们不必把#关键字定义为一个空白的macros来抑制关键字。