el uso real de la C99 'restringir' palabra clave?
-
09-09-2019 - |
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?
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
- C ++ 14 no tiene todavía un análogo para
restrict
, pero GCC ha__restrict__
como una extensión: ¿Qué significa la palabra clave restringir significar en C ++? - Muchas de las preguntas que le piden: de acuerdo a los detalles morbosos, hace este código UB o no?
- A "cuándo utilizar" pregunta: Cuando restringir el uso y cuando no
- El
__attribute__((malloc))
GCC relacionados, que dice que el valor de retorno de una función no es alias a cualquier cosa: GCC: __attribute__ ((malloc))