有没有人见过关于restrict
在 gcc/g++ 中使用 C/C++ 关键字是否在现实中(而不仅仅是在理论上)提供任何显着的性能提升的任何数字/分析?
我已经阅读了各种推荐/贬低其使用的文章,但我没有遇到任何实际证明双方论点的实数。
编辑
我知道这restrict
不是 C++ 的正式组成部分,但它受到一些编译器的支持,而且我读过Christer Ericson的一篇论文,强烈推荐使用它。
有没有人见过关于restrict
在 gcc/g++ 中使用 C/C++ 关键字是否在现实中(而不仅仅是在理论上)提供任何显着的性能提升的任何数字/分析?
我已经阅读了各种推荐/贬低其使用的文章,但我没有遇到任何实际证明双方论点的实数。
编辑
我知道这restrict
不是 C++ 的正式组成部分,但它受到一些编译器的支持,而且我读过Christer Ericson的一篇论文,强烈推荐使用它。
限制关键字有所不同。
在某些情况下(图像处理),我已经看到了因子 2 和更多的改进。大多数时候,差异并没有那么大。大约 10%。
这是一个说明差异的小例子。我写了一个非常基本的 4x4 向量 * 矩阵变换作为测试。请注意,我必须强制该函数不被内联。否则 GCC 会检测到我的基准代码中没有任何别名指针,并且由于内联,restrict 不会产生影响。
我也可以将转换函数移动到不同的文件中。
#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);
}
结果:(在我的 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
在那个系统上,执行速度提高了 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
这里的差异只有 9%(顺便说一句,相同的编译器。)
限制关键字是否在 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;
}
编译和反编译:
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
= 第三个参数结论:3 个指令而不是 4 个。
当然,指令可以有不同的延迟,但这给出了一个好主意。
为什么 GCC 能够优化它?
上面的代码取自Wikipedia 示例,该示例非常有启发性。
伪汇编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 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
对于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" is no longer needed.
load R2 ← *b
add R2 += R1
set R2 → *b
真的更快吗?
Ermmm ...不适用于这个简单的测试:
.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
接着:
as -o a.o a.S && ld a.o && time ./a.out
在 Ubuntu 14.04 AMD64 CPU Intel i5-3210M 上。
我承认我仍然不了解现代 CPU。让我知道您是否:
文章Demystifying The Restrict Keyword参考了为什么程序员指定的别名是一个坏主意(pdf) 的论文,它说它通常没有帮助并提供测量来支持这一点。
请注意,允许该restrict
关键字的 C++ 编译器可能仍会忽略它。例如这里就是这种情况。
我测试了这个C 程序。没有restrict
它需要 12.640 秒才能完成,需要 12.516 秒restrict
。看起来可以节省一些时间。