Ограничение ключевого слова обеспечивает значительные преимущества в GCC/G ++?

StackOverflow https://stackoverflow.com/questions/1965487

Вопрос

Кто -нибудь когда -нибудь видел какие -либо числа/анализ о том, использует ли 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. Похоже на то Можно спасти немного время.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top