Pregunta

estaba navegando a través de algún tipo de documentación y preguntas / respuestas y vi que se menciona. Leí una breve descripción, afirmando que sería básicamente una promesa de que el programador que el puntero no se puede utilizar para apuntar a otra parte.

Puede alguien ofrecer algunos casos reales donde su pena utilizar realmente esto?

¿Fue útil?

Solución

restrict dice que el puntero es el único que tiene acceso al objeto subyacente. Se elimina la posibilidad de aliasing puntero, lo que permite una mejor optimización por el compilador.

Por ejemplo, supongamos que tengo una máquina con instrucciones especializadas que pueden multiplicar vectores de números en la memoria, y tengo el siguiente 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];
    }
}

El compilador necesita para manejar correctamente si dest, src1 y src2 solapamiento, lo que significa que tiene que hacer una multiplicación a la vez, desde el principio hasta el final. Al tener restrict, el compilador es gratuito para optimizar el código mediante el uso de las instrucciones vectoriales.

Wikipedia tiene una entrada en restrict, con otro ejemplo, aquí .

Otros consejos

El Wikipedia ejemplo es muy iluminador.

Esto demuestra claramente cómo permite ahorrar una instrucción de montaje .

Sin limitar:

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

Montaje de Pseudo:

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

Con limitar:

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

Montaje de Pseudo:

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

¿El GCC realmente hacerlo?

GCC 4.8 x86-64 Linux:

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

salida GCC fue incluso más claro que el artículo wiki: 4 instrucciones vs 3 Instrucciones

.

Las matrices

Hasta el momento tenemos un ahorro de instrucciones individuales, pero si el puntero representan matrices para ser puesto en loop, un caso de uso común, entonces un montón de instrucciones podrían ahorrarse, según lo mencionado por supercat .

Considere por ejemplo:

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

Debido a restrict, un compilador inteligente (o humano), que podría optimizar a:

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

que es potencialmente mucho más eficiente, ya que puede ser de montaje optimizado en una implementación libc decente (como glibc): ¿es mejor utilizar std :: memcpy () o std :: copy () en cuanto a rendimiento?

¿El GCC realmente hacerlo?

GCC 5.2.1.Linux x86-64 Ubuntu 15.10:

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

Con -O0, ambos son lo mismo.

Con -O3:

  • con restrinjan:

    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
    

    Dos llamadas memset como se esperaba.

  • sin restringir: no hay llamadas stdlib, sólo un 16 iteración amplia bucle desenrollar el que no tengo la intención de reproducir aquí: -)

No he tenido la paciencia de referencia, pero yo creo que la versión restringir será más rápido.

C99

Veamos el estándar por el bien completo.

restrict dice que dos punteros no pueden apuntar a la superposición de las regiones de memoria. El uso más común es para argumentos de la función.

Esto restringe la forma en la función puede ser llamada, pero permite más optimizaciones en tiempo de compilación.

Si la persona que llama no se sigue el contrato restrict, un comportamiento indefinido.

El C99 N1256 proyecto 6.7. 3/7 "calificadores de tipo" dice:

  

El uso previsto de la clasificación para restringir (como la clase de registros de almacenamiento) es promover la optimización y eliminación de todas las instancias de la clasificación de todas las unidades de traducción de pre-procesamiento que componen un programa conforme no cambia su significado (es decir, el comportamiento observable).

y 6.7.3.1 "definición formal de restringir" da los detalles morbosos.

regla de alias estricto

La palabra clave restrict sólo afecta a los punteros de tipos compatibles (por ejemplo, dos int*) debido a las estrictas reglas de alias dice que aliasing tipos incompatibles es un comportamiento no definido por defecto, y así compiladores puede asumir que no suceda y optimizar de distancia.

Ver: ¿Cuál es la regla de alias estricto

Ver también

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