c - título - title tag e title heading




Uso realista da palavra-chave 'restrita' C99? (2)

Eu estava folheando algumas documentações e perguntas / respostas e vi isso mencionado. Eu li uma breve descrição, afirmando que seria basicamente uma promessa do programador que o ponteiro não será usado para apontar para outro lugar.

Alguém pode oferecer alguns casos realistas em que vale a pena usar isso?


O exemplo da Wikipedia é muito esclarecedor.

Mostra claramente como permite salvar uma instrução de montagem .

Sem restringir:

void f(int *a, int *b, int *x) {
  *a += *x;
  *b += *x;
}

Pseudo montagem:

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

Com restringir:

void fr(int *restrict a, int *restrict b, int *restrict x);

Pseudo montagem:

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

O GCC realmente faz isso?

GCC 4.8 Linux x86-64:

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, a convenção de chamada é:

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

A saída do GCC foi ainda mais clara do que o artigo da wiki: 4 instruções versus 3 instruções.

Matrizes

Até agora, temos economias de instrução única, mas se o ponteiro representar matrizes a serem colocadas em loop, um caso de uso comum, então, um monte de instruções poderia ser salvo, como mencionado pelo supercat .

Considere por exemplo:

void f(char *restrict p1, char *restrict p2) {
    for (int i = 0; i < 50; i++) {
        p1[i] = 4;
        p2[i] = 9;
    }
}

Por causa da restrict , um compilador inteligente (ou humano) poderia otimizá-lo para:

memset(p1, 4, 50);
memset(p2, 9, 50);

que é potencialmente muito mais eficiente, pois pode ser otimizada para montagem em uma implementação de libc decente (como glibc): É melhor usar std :: memcpy () ou std :: copy () em termos de desempenho?

O GCC realmente faz isso?

GCC 5.2.1.Linux x86-64 Ubuntu 15.10:

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

Com -O0 , ambos são iguais.

Com -O3 :

  • com restrito:

    3f0:   48 85 d2                test   %rdx,%rdx
    3f3:   74 33                   je     428 <fr+0x38>
    3f5:   55                      push   %rbp
    3f6:   53                      push   %rbx
    3f7:   48 89 f5                mov    %rsi,%rbp
    3fa:   be 04 00 00 00          mov    $0x4,%esi
    3ff:   48 89 d3                mov    %rdx,%rbx
    402:   48 83 ec 08             sub    $0x8,%rsp
    406:   e8 00 00 00 00          callq  40b <fr+0x1b>
                            407: R_X86_64_PC32      memset-0x4
    40b:   48 83 c4 08             add    $0x8,%rsp
    40f:   48 89 da                mov    %rbx,%rdx
    412:   48 89 ef                mov    %rbp,%rdi
    415:   5b                      pop    %rbx
    416:   5d                      pop    %rbp
    417:   be 09 00 00 00          mov    $0x9,%esi
    41c:   e9 00 00 00 00          jmpq   421 <fr+0x31>
                            41d: R_X86_64_PC32      memset-0x4
    421:   0f 1f 80 00 00 00 00    nopl   0x0(%rax)
    428:   f3 c3                   repz retq

    Dois memset chama conforme o esperado.

  • sem restrito: sem chamadas stdlib, apenas um desenrolamento de 16 iterações que não pretendo reproduzir aqui :-)

Eu não tive paciência para compará-los, mas acredito que a versão restrita será mais rápida.

C99

Vamos olhar para o padrão por completo.

restrict diz que dois ponteiros não podem apontar para sobrepor regiões de memória. O uso mais comum é para argumentos de função.

Isso restringe como a função pode ser chamada, mas permite mais otimizações em tempo de compilação.

Se o chamador não seguir o contrato restrict , o comportamento indefinido.

O C99 N1256 draft 6.7.3 / 7 "Type qualifiers" diz:

O uso pretendido do qualificador restrito (como a classe de armazenamento de registradores) é promover a otimização, e excluir todas as instâncias do qualificador de todas as unidades de tradução de pré-processamento que compõem um programa em conformidade não altera seu significado (ou seja, comportamento observável).

e 6.7.3.1 "Definição formal de restrição" fornece detalhes sangrentos.

Regra de aliasing estrita

A palavra-chave restrict só afeta ponteiros de tipos compatíveis (por exemplo, dois int* ) porque as regras de aliasing estritas dizem que o aliasing de tipos incompatíveis é um comportamento indefinido por padrão, e assim os compiladores podem assumir que isso não acontece e otimizar.

Veja: Qual é a regra de aliasing estrita?

Veja também


restrict diz que o ponteiro é a única coisa que acessa o objeto subjacente. Ele elimina o potencial de aliasing de ponteiro, permitindo melhor otimização pelo compilador.

Por exemplo, suponha que eu tenha uma máquina com instruções especializadas que possam multiplicar vetores de números na memória e eu tenho o seguinte código:

void MultiplyArrays(int* dest, int* src1, int* src2, int n)
{
    for(int i = 0; i < n; i++)
    {
        dest[i] = src1[i]*src2[i];
    }
}

O compilador precisa manipular corretamente se dest , src1 e src2 sobrepuserem, o que significa que ele deve fazer uma multiplicação por vez, do início ao fim. Por restrict , o compilador está livre para otimizar esse código usando as instruções de vetor.

A Wikipedia tem uma entrada restrict , com outro exemplo here .





restrict-qualifier