¿Qué significa la palabra clave restringir en C ++?

182

Siempre estaba inseguro, ¿qué significa la palabra clave restringir en C ++?

¿Significa que los dos o más punteros dados a la función no se superponen? ¿Qué más significa?

Estera
fuente
23
restrictes una palabra clave c99. Sí, Rpbert S. Barnes, sé que la mayoría de los compiladores son compatibles __restrict__. Notará que cualquier cosa con guiones bajos dobles es, por definición, específica de implementación y, por lo tanto, NO C ++ , sino una versión específica del compilador.
KitsuneYMG
55
¿Qué? El hecho de que su implementación sea específica no hace que no sea C ++; C ++ permite la implementación de material específico de manera explícita, y no lo deshabilita ni lo convierte en C ++.
Alice
44
@Alice KitsuneYMG significa que no es parte de ISO C ++ y, en cambio, se considera una extensión de C ++. Los creadores de compiladores pueden crear y distribuir sus propias extensiones, que coexisten con ISO C ++ y actúan como parte de una adición no oficial a C ++, por lo general, menos o no portátil. Los ejemplos serían el antiguo C ++ administrado de MS y su C ++ / CLI más reciente. Otros ejemplos serían las directivas de preprocesador y las macros proporcionadas por algunos compiladores, como la #warningdirectiva común o las macros de firma de funciones ( __PRETTY_FUNCTION__en GCC, __FUNCSIG__en MSVC, etc.).
Justin Time - Restablece a Monica el
44
@Alice Que yo sepa, C ++ 11 no exige soporte completo para todo C99, ni C ++ 14 o lo que sé de C ++ 17. restrictno se considera una palabra clave de C ++ (consulte en.cppreference.com/w/cpp/keyword ), y de hecho, la única mención restricten el estándar C ++ 11 (consulte open-std.org/jtc1/sc22/wg21 /docs/papers/2012/n3337.pdf , una copia del FDIS con pequeños cambios editoriales, §17.2 [library.c], PDF página 413) establece que:
Justin Time - Restablece a Monica el
44
@Alice ¿Cómo es eso? Indiqué la parte que dice que restrictse debe omitir (excluir, excluir) de las firmas y semánticas de la función de biblioteca estándar de C cuando esas funciones se incluyen en la biblioteca estándar de C ++. O en otras palabras, dije el hecho que dice que si la firma de una función de biblioteca estándar de C contiene restrictC, la restrictpalabra clave debe eliminarse de la firma del equivalente de C ++.
Justin Time - Restablece a Monica el

Respuestas:

143

En su artículo, Memory Optimization , Christer Ericson dice que mientrasrestrict todavía no forma parte del estándar C ++, que es compatible con muchos compiladores y recomienda su uso cuando esté disponible:

restringir palabra clave

! Nuevo en 1999 ANSI / ISO C estándar

! Todavía no está en el estándar C ++, pero es compatible con muchos compiladores C ++

! Solo una pista, por lo que puede no hacer nada y seguir siendo conforme

Un puntero restringido calificado (o referencia) ...

! ... es básicamente una promesa al compilador de que, para el alcance del puntero, solo se podrá acceder al objetivo del puntero a través de ese puntero (y los punteros copiados de él).

En los compiladores de C ++ que lo admiten, probablemente debería comportarse igual que en C.

Vea esta publicación SO para más detalles: Uso realista de la palabra clave 'restringir' C99?

Tómese media hora para leer el papel de Ericson, es interesante y vale la pena.

Editar

También descubrí que el compilador AIX C / C ++ de__restrict__ IBM admite la palabra clave .

g ++ también parece admitir esto ya que el siguiente programa se compila limpiamente en g ++:

#include <stdio.h>

int foo(int * __restrict__ a, int * __restrict__ b) {
    return *a + *b;
}

int main(void) {
    int a = 1, b = 1, c;

    c = foo(&a, &b);

    printf("c == %d\n", c);

    return 0;
}

También encontré un buen artículo sobre el uso de restrict:

Desmitificando la palabra clave restringida

Edit2

Me encontré con un artículo que analiza específicamente el uso de restrict en los programas de C ++:

Load-hit-stores y la palabra clave __restrict

Además, Microsoft Visual C ++ también admite la __restrictpalabra clave .

Robert S. Barnes
fuente
2
El enlace de papel de Optimización de memoria está muerto, aquí hay un enlace al audio de su presentación de GDC. gdcvault.com/play/1022689/Memory
Grimeh
1
@EnnMichael: Obviamente, si vas a usarlo en un proyecto portátil de C ++, deberías #ifndef __GNUC__ #define __restrict__ /* no-op */o similar. Y definirlo a __restrictsi _MSC_VERestá definido.
Peter Cordes
96

Como otros dijeron, si no significa nada a partir de C ++ 14 , entonces consideremos la __restrict__extensión GCC que hace lo mismo que el C99restrict .

C99

restrictdice que dos punteros no pueden apuntar a regiones de memoria superpuestas. El uso más común es para argumentos de función.

Esto restringe cómo se puede llamar a la función, pero permite más optimizaciones de compilación.

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

El C99 N1256 borrador 6.7.3 / 7 "Calificadores de tipo" dice:

El uso previsto del calificador de restricción (como la clase de almacenamiento de registro) es para promover la optimización, y eliminar todas las instancias del calificador de todas las unidades de traducción de preprocesamiento que componen un programa conforme no cambia su significado (es decir, comportamiento observable).

y 6.7.3.1 "Definición formal de restricción" da los detalles sangrientos.

Una posible optimización

El ejemplo de Wikipedia es muy esclarecedor.

Muestra claramente cómo, ya que permite guardar una instrucción de ensamblaje .

Sin restricción:

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

Pseudo ensamblaje:

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 x may point to a (a aliased by x) thus 
; the value of x will change when the value of a
; changes.
load R1  *x
load R2  *b
add R2 += R1
set R2  *b

Con restringir:

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

Pseudo ensamblaje:

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" is no longer needed.
load R2  *b
add R2 += R1
set R2  *b

¿GCC realmente lo hace?

g++ 4.8 Linux x86-64:

g++ -g -std=gnu++98 -O0 -c main.cpp
objdump -S main.o

Con -O0, son lo mismo.

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, la convención de convocatoria es:

  • rdi = primer parámetro
  • rsi = segundo parámetro
  • rdx = tercer parámetro

La salida de GCC fue incluso más clara que el artículo wiki: 4 instrucciones vs 3 instrucciones.

Matrices

Hasta ahora, tenemos ahorros en una sola instrucción, pero si el puntero representa las matrices que se van a recorrer, un caso de uso común, entonces se podrían guardar un montón de instrucciones, como lo mencionaron supercat y michael .

Considere por ejemplo:

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

Debido a que restrictun compilador inteligente (o humano), podría optimizar eso para:

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

¿Qué es potencialmente mucho más eficiente, ya que puede ser ensamblado optimizado en una implementación decente de libc (como glibc) ¿Es mejor usar std :: memcpy () o std :: copy () en términos de rendimiento? , posiblemente con instrucciones SIMD .

Sin, restringir, esta optimización no podría hacerse, por ejemplo, considere:

char p1[4];
char *p2 = &p1[1];
f(p1, p2, 3);

Entonces la forversión hace:

p1 == {4, 4, 4, 9}

mientras que la memsetversión hace:

p1 == {4, 9, 9, 9}

¿GCC realmente lo hace?

GCC 5.2.1.Linux x86-64 Ubuntu 15.10:

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

Con -O0ambos son lo mismo.

Con -O3:

  • con restricción:

    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 memsetllamadas como se esperaba.

  • sin restricción: no hay llamadas stdlib, solo un desenrollamiento de bucle ancho de 16 iteraciones que no pretendo reproducir aquí :-)

No he tenido la paciencia para compararlos, pero creo que la versión restringida será más rápida.

Regla de alias estricta

La restrictpalabra clave solo afecta a punteros de tipos compatibles (por ejemplo, dos int*) porque las estrictas reglas de alias dicen que el alias de tipos incompatibles es un comportamiento indefinido por defecto, por lo que los compiladores pueden asumir que no sucede y optimizar.

Ver: ¿Cuál es la estricta regla de alias?

¿Funciona para referencias?

De acuerdo con los documentos de GCC, sí lo hace: https://gcc.gnu.org/onlinedocs/gcc-5.1.0/gcc/Restricted-Pointers.html con sintaxis:

int &__restrict__ rref

Incluso hay una versión para las thisfunciones miembro:

void T::fn () __restrict__
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
agradable respuesta. ¿Qué pasa si el alias estricto está deshabilitado -fno-strict-aliasing, entonces no restrictdebería haber diferencia entre punteros del mismo tipo o tipos diferentes, no? (Me refiero a "La palabra clave restringir solo afecta a punteros de tipos compatibles")
idclev 463035818
@ tobi303 ¡No lo sé! Avíseme si lo sabe con seguridad ;-)
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
@jww sí, esa es una mejor manera de redactarlo. Actualizado.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
restrictsignifica algo en C ++. Si llama a una función de biblioteca C con restrictparámetros de un programa C ++, debe obedecer las implicaciones de eso. Básicamente, si restrictse usa en una API de biblioteca C significa algo para cualquiera que lo llame desde cualquier lenguaje, incluido el FFI dinámico de Lisp.
Kaz
22

Nada. Fue agregado al estándar C99.

Dirkgently
fuente
8
Eso no es del todo cierto. Aparentemente es compatible con algunos compiladores de C ++ y algunas personas recomiendan encarecidamente su uso cuando esté disponible, vea mi respuesta a continuación.
Robert S. Barnes
18
@Robert S Barnes: el estándar C ++ no se reconoce restrictcomo una palabra clave. Por lo tanto, mi respuesta es correcta. Lo que describe es un comportamiento específico de implementación y algo en lo que realmente no debe confiar.
Dirkgently
27
@dirkgently: Con el debido respeto, ¿por qué no? Muchos proyectos están vinculados a extensiones de lenguaje no estándar específicas compatibles solo con compiladores específicos o muy pocos. Me viene a la mente el kernel de Linux y gcc. No es raro quedarse con un compilador específico, o incluso una revisión específica de un compilador específico durante toda la vida útil de un proyecto. No todos los programas deben cumplir estrictamente.
Robert S. Barnes
77
@Rpbert S. Barnes: No puedo enfatizar más por qué no debes depender de un comportamiento específico de implementación. En cuanto a Linux y gcc, piense y verá por qué no son un buen ejemplo en su defensa. Todavía tengo que ver incluso una pieza de software moderadamente exitosa ejecutada en una sola versión de compilador durante toda su vida útil.
Dirkgently
16
@Rpbert S. Barnes: La pregunta decía c ++. No MSVC, no gcc, no AIX. Si acidzombie24 quisiera extensiones específicas del compilador, debería haberlo dicho / etiquetado así.
KitsuneYMG
12

Esta es la propuesta original para agregar esta palabra clave. Sin embargo, como se señaló puntualmente, esta es una característica de C99 ; No tiene nada que ver con C ++.

relajarse
fuente
55
Muchos compiladores de C ++ admiten la __restrict__palabra clave que es idéntica por lo que puedo decir.
Robert S. Barnes
Tiene todo que ver con C ++, porque los programas de C ++ llaman bibliotecas C, y las bibliotecas C usan restrict. El comportamiento del programa C ++ se vuelve indefinido si viola las restricciones implicadas por restrict.
Kaz
@kaz Totalmente equivocado. No tiene nada que ver con C ++ porque no es una palabra clave o característica de C ++, y si usa archivos de encabezado C en C ++ debe eliminar la restrictpalabra clave. Por supuesto, si pasa punteros con alias a una función C que los declara restringidos (lo que puede hacer desde C ++ o C), entonces no está definida, pero eso depende de usted.
Jim Balter
@JimBalter Ya veo, así que lo que estás diciendo es que los programas C ++ llaman bibliotecas C, y las bibliotecas C usan restrict. El comportamiento del programa C ++ se vuelve indefinido si viola las restricciones implicadas por restrict. Pero esto en realidad no tiene nada que ver con C ++, porque está "en ti".
Kaz
5

No existe tal palabra clave en C ++. La lista de palabras clave de C ++ se puede encontrar en la sección 2.11 / 1 del estándar de lenguaje C ++. restrictes una palabra clave en la versión C99 del lenguaje C y no en C ++.

Hormiga
fuente
55
Muchos compiladores de C ++ admiten la __restrict__palabra clave que es idéntica por lo que puedo decir.
Robert S. Barnes
18
@Robert: Pero no existe esa palabra clave en C ++ . Lo que hacen los compiladores individuales es su propio negocio, pero no es parte del lenguaje C ++.
jalf
4

Dado que los archivos de encabezado de algunas bibliotecas C usan la palabra clave, el lenguaje C ++ tendrá que hacer algo al respecto ... como mínimo, ignorando la palabra clave, por lo que no tenemos que #definir la palabra clave en una macro en blanco para suprimir la palabra clave .

Johan Boulé
fuente
3
Supongo que eso se maneja mediante el uso de una extern Cdeclaración, o se descarta silenciosamente, como es el caso con el compilador AIX C / C ++, que en su lugar maneja la __rerstrict__palabra clave. Esa palabra clave también es compatible con gcc para que el código compile lo mismo con g ++.
Robert S. Barnes