restrict关键字在C ++中的含义是什么?

时间:2009-04-22 08:59:07

标签: c++ restrict-qualifier

我总是不确定,在C ++中,restrict关键字是什么意思?

是否意味着赋予函数的两个或更多指针不重叠? 还有什么意思呢?

6 个答案:

答案 0 :(得分:130)

在他的论文Memory Optimization中,Christer Ericson说虽然restrict还不是C ++标准的一部分,但它得到了许多编译器的支持,他建议在可用时使用它:

  

限制关键字

     

!新的1999 ANSI / ISO C标准

     

!尚未在C ++标准中,但受到许多C ++编译器的支持

     

!只提示,所以可能什么都不做,仍然符合

     

限制合格的指针(或参考)......

     

! ......基本上是一个   承诺编译器为        指针的范围,指针的目标只会        通过该指针访问(和指针复制        从它)。

在支持它的C ++编译器中,它应该与C中的行为相同。

有关详细信息,请参阅此SO帖子:Realistic usage of the C99 ‘restrict’ keyword?

花一半小时浏览Ericson的论文,这很有趣,值得花时间。

修改

我还发现了IBM的AIX C/C++ compiler supports the __restrict__ keyword

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的好文章:

Demystifying The Restrict Keyword

<强> EDIT2

我遇到了一篇专门讨论在C ++程序中使用restrict的文章:

Load-hit-stores and the __restrict keyword

此外,Microsoft Visual C ++ also supports the __restrict keyword

答案 1 :(得分:79)

正如其他人所说,如果在C ++ 14 中意味着,那么让我们考虑{C} __restrict__与G99 restrict相同的GCC扩展。

<强> C99

restrict说两个指针不能指向重叠的内存区域。最常见的用法是函数参数。

这限制了函数的调用方式,但允许更多的编译优化。

如果调用者未遵循restrict合同,则定义为未定义的行为。

C99 N1256 draft 6.7.3 / 7“类型限定词”说:

  

限制限定符(如寄存器存储类)的预期用途是促进优化,并且从构成符合程序的所有预处理转换单元中删除限定符的所有实例不会改变其含义(即,可观察行为)。

和6.7.3.1“限制的正式定义”给出了血淋淋的细节。

可能的优化

Wikipedia example 非常有启发性。

它清楚地显示了如何它允许保存一个汇编指令

没有限制:

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

GCC真的这么做吗?

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) 

对于没有经验的人,calling convention是:

  • rdi =第一个参数
  • rsi =第二个参数
  • rdx =第三个参数

GCC输出甚至比维基文章更清晰:4条指令与3条指令。

<强>阵列

到目前为止,我们只有单指令节省,但如果指针表示要循环的数组,这是一个常见的用例,那么可以保存一堆指令,如supercatmichael所述。

考虑例如:

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)Is it better to use std::memcpy() or std::copy() in terms to performance?上进行程序集优化,可能还有SIMD instructions

没有,限制,无法完成此优化,例如考虑:

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.Linux x86-64 Ubuntu 15.10:

gcc -g -std=c99 -O0 -c main.c
objdump -dr main.o

使用-O0,两者都相同。

使用-O3

  • with restrict:

    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迭代宽loop unrolling我不打算在这里重现: - )

我没有耐心对它们进行基准测试,但我相信限制版本会更快。

严格别名规则

restrict关键字仅影响兼容类型的指针(例如两个int*),因为严格的别名规则表明,默认情况下别名不兼容类型是未定义的行为,因此编译器可以假设它不会发生并优化了。

请参阅:What is the strict aliasing rule?

是否适用于参考文献?

根据GCC文档,它确实:https://gcc.gnu.org/onlinedocs/gcc-5.1.0/gcc/Restricted-Pointers.html语法:

int &__restrict__ rref

甚至还有this成员函数的版本:

void T::fn () __restrict__

答案 2 :(得分:17)

无。它被添加到C99标准中。

答案 3 :(得分:10)

This是添加此关键字的原始提案。尽管如此指出,这是一个C99功能;它与C ++无关。

答案 4 :(得分:5)

C ++中没有这样的关键字。 C ++关键字列表可以在C ++语言标准的2.11 / 1节中找到。 restrict是C语言的C99版本中的关键字,而不是C ++。

答案 5 :(得分:4)

由于来自某些C库的头文件使用该关键字,因此C ++语言必须对其进行一些操作..至少忽略关键字,因此我们不必将关键字#define为空白宏禁止关键字。