Pregunta

¿Alguien ha visto ningún número / análisis sobre si o no usar de la C / C ++ palabra clave restrict en gcc / g ++ real proporciona ningún aumento significativo del rendimiento en la realidad (y no sólo en teoría)?

He leído varios artículos recomendando / menospreciar su uso, pero no he corrió a través de números reales prácticamente ya sea demostrando argumentos lados.

Editar

Sé que restrict no es oficialmente parte de C ++, pero con el apoyo de algunos compiladores y he leído un artículo de Christer Ericson que recomienda encarecidamente su uso.

¿Fue útil?

Solución

La palabra clave restringir hace una diferencia.

He visto mejoras de factor de 2 y más en algunas situaciones (procesamiento de imágenes). La mayoría de las veces la diferencia no es tan grande, aunque. Como 10%.

Este es un pequeño ejemplo que ilustran la diferencia. He escrito un * matriz de 4x4 muy básico vector de transformar como una prueba. Tenga en cuenta que tengo que forzar la función de no ser inline. De lo contrario GCC detecta que no hay punteros aliasing en mi código de referencia y restringen no haría una diferencia debido a procesos en línea.

Podría haber movido la función de transformación en un archivo diferente.

#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);
}

Resultados: (en mi 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

Durante la ejecución del pulgar 20% más rápido, en que sistema.

Para mostrar lo mucho que depende de la arquitectura He dejado que el mismo código se ejecute en una CPU Cortex-A8 incrustado (ajustado el bucle contar un poco porque yo no quiero esperar tanto tiempo):

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

Aquí la diferencia es sólo el 9% (mismo compilador por cierto.)

Otros consejos

  

¿Tiene la palabra clave restringir proporcionar beneficios significativos en gcc / g ++?

puede reducir el número de instrucciones como se muestra en el ejemplo a continuación, a fin de utilizarlo siempre que sea posible.

GCC 4.8 Linux x86-64 exmample

Entrada:

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;
}

Compilar y descompilar:

gcc -g -std=c99 -O0 -c main.c
objdump -S main.o

Con -O0, que son los mismos.

Con -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) 

Para los no iniciados, el convención de llamada es:

  • rdi = primer parámetro
  • rsi = segundo parámetro
  • rdx = tercer parámetro

Conclusión: 3 instrucciones en lugar de 4

.

Por supuesto, las instrucciones puede tener diferentes latencias , pero esto da una buena idea.

¿Por qué GCC fue capaz de optimizar esa?

El código de arriba fue tomada de la ejemplo Wikipedia que es muy iluminador.

Montaje de Pseudo para 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

Para 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

¿Es realmente más rápido?

ermmm ... no por esta sencilla prueba:

.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

Y a continuación:

as -o a.o a.S && ld a.o && time ./a.out

en Ubuntu 14.04 AMD64 CPU Intel i5-3210M.

Confieso que todavía no entiendo CPU modernas. Déjeme saber si usted:

  • encontró un defecto en mi método
  • encontró un caso de prueba ensamblador donde se convierte en mucho más rápido
  • entender por qué no había una diferencia

El artículo Desmitificar La palabra clave Restringir se refiere al papel Por qué programador-Aliasing especificada es una mala idea ( pdf), que dice que por lo general no ayuda y proporciona medidas para apoyar esto.

Tenga en cuenta que los compiladores de C ++ que permiten la palabra clave restrict todavía pueden ignorarlo. Ese es el caso, por ejemplo aquí .

Probé este C-Programa . Sin restrict tardó 12.640 segundos para completar, con restrict 12.516. Parece que puede Guardar algunos tiempo.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top