C99“restrict”关键字的实际用法?

问题描述 投票:0回答:4

我正在浏览一些文档和问题/答案,并看到提到了它。我读了一个简短的描述,指出这基本上是程序员的承诺,指针不会被用来指向其他地方。

任何人都可以提供一些值得实际使用的实际案例吗?

c gcc c99 restrict-qualifier
4个回答
235
投票

restrict
表示指针是唯一访问底层对象的东西。 它消除了指针别名的可能性,从而使编译器能够更好地优化。

例如,假设我有一台带有专门指令的机器,可以将内存中的数字向量相乘,并且我有以下代码:

void MultiplyArrays(int* dest, int* src1, int* src2, int n)
{
    for(int i = 0; i < n; i++)
    {
        dest[i] = src1[i]*src2[i];
    }
}

编译器需要正确处理

dest
src1
src2
重叠,这意味着它必须从开始到结束一次执行一个乘法。 通过拥有
restrict
,编译器可以使用向量指令自由地优化此代码。

维基百科有一个关于

restrict
的条目,还有另一个例子,这里


229
投票

维基百科的例子非常很有启发性。

它清楚地显示了如何

它允许保存一条汇编指令

无限制:

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

GCC真的做到了吗?

GCC 4.8 Linux x86-64:

gcc -g -std=c99 -O0 -c main.c 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所述。

考虑例如:

void f(char *restrict p1, char *restrict p2) { for (int i = 0; i < 50; i++) { p1[i] = 4; p2[i] = 9; } }
因为

restrict

,智能编译器(或人类)可以将其优化为:

memset(p1, 4, 50); memset(p2, 9, 50);
这可能更高效,因为它可能在一个不错的 libc 实现(如 glibc)上进行了汇编优化:

就性能而言,使用 std::memcpy() 或 std::copy() 更好吗?

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

  • 有限制:

    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 个迭代宽

    循环展开,我不打算在这里重现:-)

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

C99

为了完整起见,让我们看一下标准。

restrict

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

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

如果调用者不遵守

restrict

 合约,则未定义的行为。

C99 N1256 草案 6.7.3/7“类型限定符”说:

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

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

严格的别名规则

restrict

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

参见:

什么是严格的别名规则?

另请参阅

    C++14 还没有
  • restrict
     的类似物,但 GCC 有 
    __restrict__
     作为扩展:
    restrict 关键字在 C++ 中意味着什么?
  • 很多问题问:根据血淋淋的细节,这个代码是否为UB?
    • 通过示例理解限制限定符
    • 受限指针问题
    • 将一个受限制的指针赋值给另一个指针,并使用第二个指针修改值,这样合法吗?
  • “何时使用”问题:
  • 何时使用限制,何时不使用
  • 相关的GCC
  • __attribute__((malloc))
    ,它表示函数的返回值没有任何别名:
    GCC: __attribute__((malloc))

5
投票
以下 C99 代码返回 0 或 1,具体取决于

restrict 限定符:

__attribute__((noinline)) int process(const int * restrict const a, int * const b) { *b /= (*a + 1) ; return *a + *b ; } int main(void) { int data[2] = {1, 2}; return process(&data[0], &data[0]); }
您可以使用该代码片段构建实际示例,特别是当 *

a 是循环条件时。

使用

gcc -std=c99 -Wall -pedantic -O3 main.c

进行编译。


0
投票
查看这篇文章,详细了解它如何帮助优化。

https://www.studydart.in/#/content/63/Restrict%20Qualifier%20in%20C/rest_qual.html

© www.soinside.com 2019 - 2024. All rights reserved.