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

谁能提供一些值得实际使用的实际案例?

有帮助吗?

解决方案

restrict说,指针是访问底层对象的唯一的事。它消除了潜在的指针别名,由编译器实现更好的优化。

举例来说,假设我有与可以乘数的矢量在存储器专用指令的机器,并且我有以下代码:

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

编译器需要妥善处理如果destsrc1,和src2交叠,这意味着它必须一次做一次乘法,从开始到结束。通过具有restrict,编译器是自由通过使用矢量指令,以优化此代码。

维基百科对restrict一个条目,另一实例,此处

其他提示

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

它清楚地表明了如何 它允许保存一条汇编指令.

无限制:

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真的做到了吗?

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 条指令与 3 条指令。

数组

到目前为止,我们节省了单条指令,但是如果指针表示要循环的数组(一个常见的用例),那么可以节省一堆指令,如 超级猫.

例如考虑:

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“类型限定符”说:

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

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

严格的别名规则

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

看: 严格的别名规则是什么?

也可以看看

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top