restrict关键字是否在gcc/g ++中提供了显着的好处?

Rob*_*nes 46 c c++ gcc g++ restrict-qualifier

有没有人见过任何关于restrictgcc/g ++实际使用C/C++ 关键字的数字/分析是否能在现实中提供任何显着的性能提升(而不仅仅是在理论上)?

我已经阅读了各种推荐/贬低其使用的文章,但我没有碰到任何实际数字,实际上证明了任何一方的论点.

编辑

我知道这restrict不是C++的正式部分,但它得到了一些编译器的支持,我读过Christer Ericson的一篇论文,强烈推荐使用它.

Nil*_*nck 46

restrict关键字有所不同.

我在某些情况下(图像处理)看到了因子2和更多的改进.大多数时候,差异并不大.大约10%.

这是一个说明差异的小例子.我写了一个非常基本的4x4矢量*矩阵变换作为测试.请注意,我必须强制不要内联函数.否则,GCC会检测到我的基准测试代码中没有任何别名指针,并且由于内联,限制不会产生差异.

我本可以将转换函数移动到另一个文件.

#include <math.h>

#ifdef USE_RESTRICT
#else
#define __restrict
#endif


void transform (float * __restrict dest, float * __restrict src, 
                float * __restrict matrix, int n) __attribute__ ((noinline));

void transform (float * __restrict dest, float * __restrict src, 
                float * __restrict matrix, int n)
{
  int i;

  // simple transform loop.

  // written with aliasing in mind. dest, src and matrix 
  // are potentially aliasing, so the compiler is forced to reload
  // the values of matrix and src for each iteration.

  for (i=0; i<n; i++)
  {
    dest[0] = src[0] * matrix[0] + src[1] * matrix[1] + 
              src[2] * matrix[2] + src[3] * matrix[3];

    dest[1] = src[0] * matrix[4] + src[1] * matrix[5] + 
              src[2] * matrix[6] + src[3] * matrix[7];

    dest[2] = src[0] * matrix[8] + src[1] * matrix[9] + 
              src[2] * matrix[10] + src[3] * matrix[11];

    dest[3] = src[0] * matrix[12] + src[1] * matrix[13] + 
              src[2] * matrix[14] + src[3] * matrix[15];

    src  += 4;
    dest += 4;
  }
}

float srcdata[4*10000];
float dstdata[4*10000];

int main (int argc, char**args)
{
  int i,j;
  float matrix[16];

  // init all source-data, so we don't get NANs  
  for (i=0; i<16; i++)   matrix[i] = 1;
  for (i=0; i<4*10000; i++) srcdata[i] = i;

  // do a bunch of tests for benchmarking. 
  for (j=0; j<10000; j++)
    transform (dstdata, srcdata, matrix, 10000);
}
Run Code Online (Sandbox Code Playgroud)

结果:(在我的2 Ghz Core Duo上)

nils@doofnase:~$ gcc -O3 test.c
nils@doofnase:~$ time ./a.out

real    0m2.517s
user    0m2.516s
sys     0m0.004s

nils@doofnase:~$ gcc -O3 -DUSE_RESTRICT test.c
nils@doofnase:~$ time ./a.out

real    0m2.034s
user    0m2.028s
sys     0m0.000s
Run Code Online (Sandbox Code Playgroud)

在该系统上,执行速度提高了20%.

为了显示它取决于架构的多少,我让相同的代码在Cortex-A8嵌入式CPU上运行(稍微调整了循环计数,因为我不想等待那么久):

root@beagleboard:~# gcc -O3 -mcpu=cortex-a8 -mfpu=neon -mfloat-abi=softfp test.c
root@beagleboard:~# time ./a.out

real    0m 7.64s
user    0m 7.62s
sys     0m 0.00s

root@beagleboard:~# gcc -O3 -mcpu=cortex-a8 -mfpu=neon -mfloat-abi=softfp -DUSE_RESTRICT test.c 
root@beagleboard:~# time ./a.out

real    0m 7.00s
user    0m 6.98s
sys     0m 0.00s
Run Code Online (Sandbox Code Playgroud)

这里的差异只有9%(同样的编译器顺便说一句.)

  • 干得好.有一篇关于在Cell处理器上使用restrict的文章:http://cellperformance.beyond3d.com/articles/2006/05/demystifying-the-restrict-keyword.html可能与讨论架构特定的好处有关. (2认同)
  • @Nils Pipenbrinck:顺便提一下,Ulrich Drepper发布了一个超优化矩阵的代码,作为他优化缓存和内存使用的讨论的一部分.它在这里:http://lwn.net/Articles/258188/.他对这个解决方案的每一步的讨论都在这里:http://lwn.net/Articles/255364/.与标准MM相比,他能够将执行时间缩短90%. (2认同)

Cir*_*四事件 8

restrict关键字是否在gcc/g ++中提供了显着的好处?

可以减少指令数量,如下例所示,因此请尽可能使用它.

GCC 4.8 Linux x86-64例子

输入:

void f(int *a, int *b, int *x) {
  *a += *x;
  *b += *x;
}

void fr(int *restrict a, int *restrict b, int *restrict x) {
  *a += *x;
  *b += *x;
}
Run Code Online (Sandbox Code Playgroud)

编译和反编译:

gcc -g -std=c99 -O0 -c main.c
objdump -S main.o
Run Code Online (Sandbox Code Playgroud)

-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) 
Run Code Online (Sandbox Code Playgroud)

对于没有经验的人,调用约定是:

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

结论:3条指令代替4条指令.

当然,指令可以有不同的延迟,但这提供了一个好主意.

为什么GCC能够优化它?

上面的代码取自维基百科的例子,非常有启发性.

伪装配f:

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
Run Code Online (Sandbox Code Playgroud)

用于fr:

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
Run Code Online (Sandbox Code Playgroud)

真的更快吗?

嗯...不是这个简单的测试:

.text
    .global _start
    _start:
        mov $0x10000000, %rbx
        mov $x, %rdx
        mov $x, %rdi
        mov $x, %rsi
    loop:
        # START of interesting block
        mov (%rdx),%eax
        add %eax,(%rdi)
        mov (%rdx),%eax # Comment out this line.
        add %eax,(%rsi)
        # END ------------------------
        dec %rbx
        cmp $0, %rbx
        jnz loop
        mov $60, %rax
        mov $0, %rdi
        syscall
.data
    x:
        .int 0
Run Code Online (Sandbox Code Playgroud)

然后:

as -o a.o a.S && ld a.o && time ./a.out
Run Code Online (Sandbox Code Playgroud)

在Ubuntu 14.04 AMD64 CPU Intel i5-3210M上.

我承认我仍然不了解现代CPU.如果您:让我知道:

  • 在我的方法中发现了一个缺陷
  • 发现了一个汇编程序测试用例,它变得更快
  • 明白为什么没有区别


小智 6

文章揭秘限制关键字是指为什么程序员指定的别名是一个坏主意(pdf),它说它通常没有帮助,并提供测量来支持这一点.

  • 有很多代码提供的好处很少,但也有一些提供了巨大的好处。您是否知道有任何论文表明明智地使用“限制”不会提供比那些编译器通过基于类型的别名实现的好处更大的好处? (2认同)