Ограничение ключевого слова обеспечивает значительные преимущества в GCC/G ++?
-
21-09-2019 - |
Вопрос
Кто -нибудь когда -нибудь видел какие -либо числа/анализ о том, использует ли C/C ++ или нет. restrict
Ключевое слово в GCC/G ++ Faction обеспечивает какое -либо значительное повышение производительности в реальности (и не только в теории)?
Я читал различные статьи, рекомендующие / унижают его использование, но я не сталкивался с какими -либо реальными цифрами, практически демонстрирующими аргументы любого стороны.
РЕДАКТИРОВАТЬ
я знаю это restrict
официально не является частью C ++, но он поддерживается некоторыми компиляторами, и я прочитал бумагу Кристер Эриксон который настоятельно рекомендует его использование.
Решение
Ключевое слово ограничивает разницу.
Я видел улучшения фактора 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);
}
Результаты: (на моем сердечном дуэте 2 ГГц)
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 (немного скорректировал количество циклов, потому что я не хочу ждать так долго):
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 Exmample
Вход:
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 смог это оптимизировать?
Приведенный выше код был взят из Пример Википедии который очень освещающий.
Псевдо сборка для 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
За 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
Это действительно быстрее?
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.
Признаюсь, я до сих пор не понимаю современных процессоров. Дайте мне знать, если вы:
- Нашел недостаток в моем методе
- Нашел тестовый пример ассюльянга, где он становится намного быстрее
- понять, почему не было разницы
Статья Демистификация ключевого слова ограничить относится к бумаге Почему псевдоним, определенный программистом,-плохая идея (PDF), в котором говорится, что это обычно не помогает и предоставляет измерения, чтобы подтвердить это.
Обратите внимание, что компиляторы C ++, которые разрешают restrict
Ключевое слово может все еще игнорировать его. Это так, например здесь.
Я проверил это С-программа. Без restrict
Это заняло 12,640 секунды, с restrict
12.516. Похоже на то Можно спасти немного время.