Domanda

Qualcuno ha mai visto i numeri e l'analisi di se o di non utilizzare il C/C++ restrict parola chiave per gcc/g++ reale, fornisce un significativo aumento di prestazioni in realtà (e non solo in teoria)?

Ho letto vari articoli consigliare / denigrare il suo utilizzo, ma non ho incontrato alcun numeri reali praticamente dimostrando entrambi i lati argomenti.

MODIFICA

So che restrict non è ufficialmente parte del C++, ma è supportato da alcuni compilatori e ho letto un articolo di Christer Ericson che consiglia il suo utilizzo.

È stato utile?

Soluzione

La parola chiave limitare fa la differenza.

Ho visto miglioramenti di fattore di 2 e più in alcune situazioni (elaborazione di immagini). Il più delle volte la differenza non è così grande però. Circa 10%.

Ecco un piccolo esempio che illustrano la differenza. Ho scritto un 4x4 vettore * matrice molto semplice trasformare come un test. Si noti che devo forzare la funzione di non essere inline. In caso contrario, GCC rileva che non ci sono indicazioni di aliasing nel mio codice di riferimento e limitano non sarebbe fare la differenza a causa di inline.

Ho potuto spostare la funzione di trasformazione in un file diverso pure.

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

Risultati: (sul mio 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

Nel corso del pollice di esecuzione del 20% più veloce, il che sistema.

Per mostrare quanto dipende l'architettura ho lasciato lo stesso codice eseguito su una CPU incorporato Cortex-A8 (aggiustato contare il ciclo un po 'perché io non voglio aspettare così a lungo):

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

Qui la differenza è solo il 9% (lo stesso compilatore btw.)

Altri suggerimenti

Il limitare parola chiave fornire vantaggi significativi in termini di gcc / g++ ?

Si può ridurre il numero di istruzioni, come illustrato nell'esempio riportato di seguito, in modo da utilizzare ogni volta che è possibile.

GCC 4.8 Linux x86-64 exmample

Ingresso:

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

Compilare e decompilare:

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

Con -O0, sono le stesse.

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) 

Per chi non lo sapesse, il convenzione di chiamata è:

  • rdi = primo parametro
  • rsi = secondo parametro
  • rdx = terzo parametro

Conclusione: 3 istruzioni invece di 4.

Naturalmente, istruzioni può avere diverse latenze, ma questo dà una buona idea.

Perché GCC è stato in grado di ottimizzare?

Il codice di cui sopra è stata scattata dall' Wikipedia esempio che è molto illuminante.

Pseudo assemblea per 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

Per 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

È davvero più veloce?

Ermmm...non per questo semplice test:

.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

E poi:

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

su Ubuntu 14.04 AMD64 CPU Intel i5-3210M.

Confesso che non ho ancora capito moderne Cpu.Fatemi sapere se avete:

  • trovare un difetto a mio metodo
  • trovato un assemblatore di test case in cui diventa molto più veloce
  • capire perché non c'era una differenza

L'articolo Demystifying Limita Chiave si riferisce alla carta Perché programmatore-specificato Aliasing è un Bad Idea ( pdf), che dice che in genere non aiuta e fornisce misurazioni per eseguire questa funzione.

Si noti che i compilatori C ++ che consentono la parola restrict può ancora ignorarlo. Questo è il caso, ad esempio qui .

questo C-Programma. Senza restrict ci sono voluti 12.640 secondi per completare, con restrict 12,516. Sembra che possono risparmiare alcuni di tempo.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top