¿Qué pueden hacer los seres humanos con el calificador restringido?
c99 keyword (6)
Si obtengo la palabra clave de restrict
C99 correcta, calificar un puntero con ella es una promesa hecha de que los datos a los que hace referencia no se modificarán detrás del compilador a través del aliasing.
Por el contrario, la forma en que entiendo el calificador const
es como documentación impuesta por el compilador de que un objeto determinado no se modificará detrás del código de escritura de un ser humano. El compilador puede obtener una pista como efecto secundario, pero como programador no me importa.
De manera similar, ¿sería apropiado considerar un calificador de restrict
en un prototipo de función como un requisito para que el usuario garantice el acceso exclusivo ("evitar el aliasing", o tal vez algo más importante) durante la duración de la llamada? ¿Debe ser utilizado como "documentación"?
Además, ¿hay algo que entender en el hecho de que restrict
califica un puntero en lugar de los datos a los que apunta (como lo hace const
)?
EDITAR: originalmente creía que la restrict
podría tener implicaciones con el código de subprocesos, pero esto parece incorrecto, así que elimino las referencias a los subprocesos de la pregunta para evitar confundir a los lectores.
¡La mayor parte de lo que sabes está mal!
const no garantiza que algo no cambie detrás de la parte posterior del compilador. Todo lo que hace es evitar que escribas en ese lugar. Sin embargo, algo más podría ser capaz de escribir en esa ubicación, por lo que el compilador NO puede asumir que es constante.
Como han dicho otros, el calificador de restricción se refiere al aliasing. De hecho, durante la primera ronda de estandarización de C, hubo una propuesta para una palabra clave "noalias". Desafortunadamente, la propuesta estaba bastante mal redactada: fue la única vez que Dennis Ritchie se involucró durante ese proceso, cuando escribió una carta que decía algo en el sentido de que "noalias deben irse. Esto no está abierto a negociación". "
No hace falta decir que ''noalias'' no se convirtió en parte de C. Cuando llegó el momento de volver a intentarlo, la propuesta se redactó lo suficientemente mejor como para incluir la restricción en la norma, y aunque noalias probablemente hubiera sido un nombre más significativo. para ello, ese nombre estaba tan viciado que dudo que alguien considerara siquiera intentar usarlo.
En cualquier caso, la intención principal de restringir es decirle al compilador que no habrá un alias de este elemento. Una razón para esto es permitir que las cosas se almacenen en registros temporalmente. Por ejemplo, considere algo como:
void f(int *a, int *b, int *c) {
for (int i=0; i<*a; i++)
*b += c[i];
}
El compilador realmente quiere poner i en un registro y cargar * a en un registro, así que cuando llega el momento de decidir si ejecutar otra iteración del bucle, simplemente compara los valores de los que se registran entre sí. Desafortunadamente, no puede hacer eso; si alguien que usó esta función estaba completamente loco y la llamó con a == b, cada vez que escribe a * b dentro del bucle, ese nuevo valor también es el valor de * a - por lo que tiene que leer * a de memoria en cada iteración del bucle, en caso de que quien lo haya llamado esté completamente loco. El uso de restringir le indica al compilador que puede generar código asumiendo que a y b siempre serán distintos, por lo que escribir en * a nunca cambiará * b (o viceversa).
Alguien más familiarizado con el estándar probablemente podría dar una mejor respuesta, pero le daré una oportunidad.
"Los datos no se modificarán detrás de la parte posterior del compilador" suena más como lo contrario de "volátil" para mí.
"const" significa que los datos no se modificarán frente al programador; es decir, no puede modificar los datos a través del significante marcado como "const" (escribo "significante" porque en int const *pi
, el nombre pi
no es const, pero *pi
es). Los datos pueden modificarse a través de otro significante (los datos no constantes pueden pasarse a una función como datos const, después de todo).
Que "restringir" califica los punteros es la clave. Los punteros son la única forma de obtener datos de alias en C, por lo que son la única forma de acceder a algunos datos a través de dos nombres diferentes. "restringir" tiene que ver con limitar el acceso a los datos a una ruta de acceso.
Chris Dodd tiene la descripción correcta de la palabra clave. En ciertas plataformas, puede ser muy importante por razones de rendimiento, ya que le permite al compilador saber que una vez que ha cargado los datos a través de ese puntero en un registro, no necesita hacerlo nuevamente. Sin esta garantía, el compilador debe recargar los datos a través de un puntero cada vez que se escriba cualquier otro puntero de posible aliasing, lo que puede causar un bloqueo grave en la tubería llamado una load-hit-store .
const
y restrict
son conceptos diferentes, y no es el caso que const
implique restrict
. Todo lo que dice const
es que no escribirá a través de ese puntero dentro del alcance de esa función . Un puntero de const
puede ser todavía alias. Por ejemplo considere:
int foo( const int *a, int * b )
{
*b *= 2;
return *a + *b; // induces LHS: *a must be read back immediately
// after write has cleared the store queue
}
Si bien no puede escribir directamente en esta función, sería perfectamente legal que llame a foo como:
int x = 3;
foo( &x, &x ); // returns 12
restrict
es una garantía diferente: una promesa de que a != b
en todas las llamadas a foo()
.
He escrito sobre la palabra clave de restrict
y sus implicaciones de rendimiento en detalle , y también lo ha hecho Mike Acton . Aunque hablamos de un PowerPC específico en el orden, el problema de carga-golpe-almacén también existe en el x86, pero la ejecución fuera de orden del x86 hace que sea más difícil aislarlo en un perfil.
Y solo para enfatizar: esto no es una optimización arcana o prematura, si te importa el rendimiento. restrict
puede llevar a aceleraciones realmente significativas si se usa correctamente.
Este podría ser un ejemplo de un dominio extremadamente estrecho, pero la plataforma Nios II de Altera es un microcontrolador de núcleo blando que puede personalizar dentro de un FPGA. Luego, dentro del código fuente de C para ese micro, puede usar una herramienta de C a hardware para acelerar los bucles internos utilizando hardware personalizado, en lugar de software.
Allí, el uso de la palabra clave __restrict__
(que es igual a la restrict
de C99) permite que la herramienta C2H optimice correctamente la aceleración de hardware de la operación del puntero en paralelo en lugar de hacerlo de forma secuencial. Al menos en este caso, la restrict
simplemente no está destinada al consumo humano. Vea también la página de Sun en restrict
, donde la primera línea dice
El uso adecuado del calificador de
restrict
en los programas de C puede permitir que el compilador produzca ejecutables significativamente más rápidos.
Si alguien está interesado en leer más sobre C2H, este PDF trata sobre cómo optimizar los resultados de C2H. La sección sobre __restrict__
está en la página 20.
La mejor ''intuición'' sobre la palabra clave de restricción es que es una garantía (por parte del programador al compilador) de que, durante la vida útil del puntero, solo se podrá acceder a la memoria a la que se accede a través de ese puntero y no a través de otro puntero. o referencia o dirección global. Por lo tanto, es importante que esté en un puntero, ya que es una propiedad tanto del puntero como de la memoria, uniendo los dos juntos hasta que el puntero quede fuera del alcance.
Su comprensión es en gran parte correcta. El calificador de restrict
simplemente establece que solo se puede acceder a los datos a los que accede un puntero calificado. Se aplica tanto a las lecturas como a las escrituras.
Al compilador no le importan los subprocesos concurrentes, no iba a generar código de manera diferente, y puede obstruir sus propios datos como desee. Pero sí necesita saber qué operaciones de puntero pueden cambiar qué memoria global.
Restrict
también lleva consigo una advertencia de la API a los humanos de que una función determinada se implementa con el supuesto de parámetros no controlados.
No es necesario que el usuario bloquee el compilador. Solo quiere asegurarse de que lee correctamente los datos que se suponía que debían ser bloqueados, por el código que el compilador debía generar , en caso de que no haya un calificador de restrict
. Agregar restrict
libera de esa preocupación.
Finalmente, tenga en cuenta que es probable que el compilador ya esté analizando un posible alias en función de los tipos de datos, en los niveles de optimización más altos, por lo que la restrict
es importante principalmente para funciones con múltiples punteros al mismo tipo de datos. Puede tomar una lección de este tema y asegurarse de que cualquier alias deliberada que haga se haga a través de un union
.
Podemos ver restrict
en acción:
void move(int *a, int *b) { void move(int *__restrict a, int *__restrict b) {
a[0] = b[0]; a[0] = b[0];
a[1] = b[0]; a[1] = b[0];
} }
movl (%edx), %eax movl (%edx), %edx
movl %eax, (%ecx) movl %edx, (%eax)
movl (%edx), %eax movl %edx, 4(%eax)
movl %eax, 4(%ecx)
En la columna de la derecha, con restrict
, el compilador no necesitaba volver a leer b[0]
de la memoria. Pudo leer b[0]
y mantenerlo en el registro %edx
, y luego almacenar el registro dos veces en la memoria. En la columna de la izquierda, no sabía si la tienda a podría haber cambiado b
.