Pergunta

Alguém já viu algum número/análise sobre se o uso do C/C ++ restrict A palavra -chave no GCC/G ++ Real fornece algum aumento significativo de desempenho na realidade (e não apenas em teoria)?

Eu li vários artigos recomendando / depreciando seu uso, mas não encontrei números reais praticamente demonstrando os argumentos de ambos os lados.

EDITAR

eu sei que restrict não faz parte oficialmente do C ++, mas é apoiado por alguns compiladores e eu li um artigo por Christer Ericson que recomenda fortemente seu uso.

Foi útil?

Solução

A palavra -chave restrita faz uma diferença.

Vi melhorias do fator 2 e mais em algumas situações (processamento de imagens). Na maioria das vezes, a diferença não é tão grande assim. Cerca de 10%.

Aqui está um pequeno exemplo que ilustram a diferença. Escrevi uma transformação de matriz 4x4 Vector * muito básica como teste. Observe que eu tenho que forçar a função a não estar inlinada. Caso contrário, o GCC detecta que não há nenhum alias em meu código de referência e restringir não faria diferença devido ao inline.

Eu poderia ter movido a função de transformação para um arquivo diferente também.

#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: (na minha dupla de 2 GHz)

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

Sobre o polegar 20% de execução mais rápida, em este sistema.

Para mostrar quanto depende da arquitetura, deixei o mesmo código executar em uma CPU incorporada Cortex-A8 (ajustei a contagem de loop um pouco porque não quero esperar tanto tempo):

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

Aqui a diferença é apenas 9% (o mesmo compilador btw.)

Outras dicas

A palavra -chave restrita oferece benefícios significativos no GCC / G ++?

Isto posso Reduza o número de instruções, conforme mostrado no exemplo abaixo; portanto, use -o sempre que possível.

GCC 4.8 Linux x86-64 AMMAMPLO

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 e decompilar:

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

Com -O0, eles são os mesmos.

Com -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 os não iniciados, o Convenção de Chamada é:

  • rdi = primeiro parâmetro
  • rsi = segundo parâmetro
  • rdx = terceiro parâmetro

Conclusão: 3 instruções em vez de 4.

Claro, instruções pode ter latências diferentes, mas isso dá uma boa ideia.

Por que o GCC conseguiu otimizar isso?

O código acima foi retirado do Exemplo da Wikipedia qual é muito iluminante.

Pseudo Assembléia 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

Por 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

É realmente mais rápido?

Ermmm ... não para este teste simples:

.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 depois:

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

No Ubuntu 14.04 AMD64 CPU Intel I5-3210m.

Confesso que ainda não entendo as CPUs modernas. Deixe me saber se você:

  • Encontrei uma falha no meu método
  • encontrou um caso de teste de montagem, onde se torna muito mais rápido
  • entender por que não havia uma diferença

O artigo Desmistificando a palavra -chave restrita refere -se ao papel Por que o alias especificado pelo programador é uma má ideia (PDF), que diz que geralmente não ajuda e fornece medições para fazer backup isso.

Observe que os compiladores C ++ que permitem o restrict A palavra -chave ainda pode ignorá -la. Esse é o caso, por exemplo aqui.

eu testei isto Programa C. Sem restrict Demorou 12.640 segundos para ser concluído, com restrict 12.516. Parece que sim posso Salve  algum Tempo.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top