¿Qué pueden hacer los seres humanos con el calificador restringido?

13 minutos de lectura

avatar de usuario
Jérémie Koenig

Si tengo el C99 restrict palabra clave correcta, calificar un puntero con él es una promesa hecha de que los datos a los que hace referencia no se modificarán a espaldas del compilador a través de alias.

Por el contrario, la forma en que entiendo el const El calificador es como documentación impuesta por el compilador de que un objeto determinado no se modificará a espaldas de un ser humano que escribe código. El compilador puede recibir una pista como efecto secundario, pero como programador realmente no me importa.

De manera similar, ¿sería apropiado considerar una restrict calificador en un prototipo de función como requisito de que el usuario asegure acceso exclusivo (“evitar aliasing”, o tal vez algo más fuerte) durante la duración de la llamada? ¿Debería usarse 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 const hace) ?

EDITAR: Originalmente creía que restrict podría tener implicaciones con el código subproceso, pero esto parece incorrecto, por lo que elimino las referencias a subprocesos de la pregunta para evitar confundir a los lectores.

  • Bueno, hay una palabra clave C de la que nunca había oído hablar antes…

    –Nick Bedford

    1 de octubre de 2009 a las 22:23

  • @Nick Bedford: Está en C99, no en C89. Entonces, solo diez años una palabra clave estándar…

    –Jonathan Leffler

    1 de octubre de 2009 a las 22:45

  • @Jeremie: ¿alguna idea de lo que significa un puntero restringido volátil? Creo que es la ausencia de la volatilidad lo que significa que un puntero restringido no se modificará a espaldas del compilador. Restringido ‘más o menos’ significa que no hay forma de que los parámetros de la función se superpongan en la memoria. Consulte la discusión de restict en stackoverflow.com/questions/1485505 y los enlaces desde allí (los enlaces a un PEP de Python y a los documentos de Cell Project).

    –Jonathan Leffler

    1 de octubre de 2009 a las 22:54

  • @Jonathan: Un volatile restrict se debe acceder al puntero exactamente una vez por cada vez que indica el código fuente, pero el compilador puede reordenar esos accesos con acceso a variables no volátiles. si es simplemente volatile pero no también restrictno solo tiene que leer el objetivo cada vez, sino que tiene que esperar para hacerlo hasta después de escribir cualquier otra variable que pueda crear un alias para el objetivo.

    – Ben Voigt

    6 de enero de 2011 a las 19:54

avatar de usuario
Crashworks

Chris Dodd tiene la descripción correcta de la palabra clave. En ciertas plataformas puede ser muy importante por motivos de rendimiento, porque le permite al compilador saber que una vez que ha cargado datos a través de ese puntero en un registro, no necesita hacerlo de nuevo. Sin esta garantía, el compilador debe volver a cargar los datos a través de un puntero cada vez que se escribe a través de cualquier otro puntero de posible alias, lo que puede provocar un bloqueo grave de la canalización llamado carga-hit-tienda.

const y restrict son conceptos diferentes, y no es el caso que const implica restrict. Todos const dice es que no vas a escribir a través de ese puntero en el ámbito de esa función. UN const el puntero aún puede tener un 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 a a en esta función, sería perfectamente legal que llamaras 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 el restrict palabra clave y sus implicaciones de rendimiento en detalley Mike Acton también. Aunque hablamos de un PowerPC en orden específico, el problema de carga-golpe-almacenamiento también existe en el x86, pero la ejecución desordenada del x86 hace que sea más difícil aislar ese bloqueo en un perfil.

Y solo para enfatizar: esto es no una optimización arcana o prematura, si le importa el rendimiento. restrict puede conducir a aceleraciones realmente significativas si se usa correctamente.

  • No creo que el ejemplo resulte necesariamente en *b se vuelve a leer inmediatamente (gcc con -O3 hace lo que le gustaría: lee a y b de la pila en edx y ecx respectivamente, luego hace mov (%ecx),%eax, add %eax,%eax, mov %eax,(%exc), mov (%edx),%ecx, pop %ebp, add %ecx,%eax, ret). Si *a también se modificaron en la función por supuesto que sería una historia diferente.

    –Steve Jessop

    1 de octubre de 2009 a las 23:28

  • mov %eax,(%exc), mov (%edx),%ecx es la tienda de carga-hit allí mismo. Sin embargo, quise decir *a en lugar de *b.

    – Crashworks

    1 de octubre de 2009 a las 23:42

  • Entonces, ¿eso se detiene incluso si ecx y edx tienen valores diferentes? no sabia eso Agregar restricción no cambia la salida de gcc 3 en absoluto, ni siquiera se carga desde edx/a antes. Entonces parece que con ese compilador, el LHS es inevitable sin importar lo que hagas.

    –Steve Jessop

    1 de octubre de 2009 a las 23:52

  • Se detiene (dentro de la canalización de la CPU) si edx y ecx tienen el mismo valor. No lo hace si una carga no llega a la otra tienda. Es por eso que los LHS son insidiosos: no puedes verlos mirando el código dentro de una función; el problema viene en el aliasing de los parámetros que le pasaron. que restringir debería do en este caso es mov (%ecx),%eax ; mov (%edx),%ebx; agregar %ebx, %ebx ; agregar %eax, %ebx ; ret. Y sí, GCC emite más WTF de lo que le corresponde.

    – Crashworks

    1 de octubre de 2009 a las 23:55

  • El ejemplo más serio del problema sería si escribiera, por ejemplo. *a += 3 antes de *b *= 2; en ese caso, una carga inevitablemente tendría que llegar a una tienda incluso si los punteros no se alias porque el compilador tendría que volver a cargar *a después de tocar *b; y *a acababa de escribirse.

    – Crashworks

    2 de octubre de 2009 a las 0:01

avatar de usuario
chris dodd

La mejor ‘intuición’ que se puede tener sobre la palabra clave restrict es que es una garantía (del programador al compilador) de que, durante la vida útil del puntero, SÓLO se accederá a la memoria a través de ese puntero a través de ese puntero y no a través de otro puntero. o referencia o dirección global. Entonces, es importante que esté en un puntero, ya que es una propiedad tanto del puntero como de la memoria, uniendo los dos hasta que el puntero se salga del alcance.

avatar de usuario
ataúd de jerry

¡La mayor parte de lo que sabes está mal!

const hace no garantía de que algo no cambiará a espaldas del compilador. Todo lo que hace es detener usted de escribir a ese lugar. Sin embargo, algo más podría 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 trata de 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 escrita: provocó 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 debe 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 escribió lo suficientemente mejor como para incluir restrict en el estándar, y aunque noalias probablemente habría sido un nombre más significativo. para él, ese nombre estaba tan contaminado que dudo que alguien siquiera haya considerado intentar usarlo.

En cualquier caso, la intención principal de restrict es decirle al compilador que no habrá un alias para 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, por lo que cuando llega el momento de decidir si ejecutar otra iteración del ciclo, simplemente compara los valores en esos registros 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 en *b dentro del ciclo, ese nuevo valor también es el valor de *a — entonces tiene que leer *a de la memoria en cada iteración del ciclo, por si acaso quien lo llamó estaba completamente loco. El uso de restrict le dice al compilador que puede generar código suponiendo que a y b siempre serán distintos, por lo que escribir en *a nunca cambiará *b (o viceversa).

  • en realidad, no se permite que los valores cambien detrás de los compiladores, ya sea const está presente o no; si los valores cambian, tendrá que agregar el volatile Calificatorio

    – Cristóbal

    2 de octubre de 2009 a las 6:19

  • Sí y no: volátil significa que desea que el compilador genere un código que tenga en cuenta la posibilidad de que cambie a sus espaldas.

    – Jerry Ataúd

    2 de octubre de 2009 a las 21:48

  • Me pregunto cómo habría funcionado “noalias” y qué aspectos objetó más Ritchie. Creo que C se habría beneficiado de una directiva “_cacheable (lvalue)” que especificaría que cualquier lectura del lvalue o los lvalues ​​derivados de él dentro de la declaración pueden secuenciarse arbitrariamente a cualquier momento anterior dentro de la declaración, y las escrituras pueden ser arbitrariamente secuenciado en cualquier momento posterior dentro de la instrucción. Dada la declaración global int x;y dentro de una función int * restrict px=&x; foo(*px); foo(*px); un compilador podría cargar x una vez en un registro guardado por destinatario, páselo a fooy luego…

    – Super gato

    16/08/2015 a las 22:30


  • …pasarlo a foo de nuevo sin tener que volver a cargarlo, pero a menos que un compilador reconozca que px fue constante y reconoció los efectos de restrictel código de máquina generado para el último enfoque podría terminar más lento de lo que hubiera sido si x simplemente se cargaron dos veces. UN _cacheable(x) La directiva podría haber sido más fácil de leer para los humanos y de procesar para el compilador.

    – Super gato

    16/08/2015 a las 22:32

avatar de usuario
DigitalRoss

Su comprensión es en gran medida correcta. Él restrict El calificador simplemente establece que los datos a los que accede un puntero así calificado son solamente accedido por ese puntero exacto. 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 eliminar 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 API a los humanos de que una función dada se implementa con la suposición de parámetros sin alias.

No es necesario ningún bloqueo por parte del usuario en lo que respecta al compilador. Solo quiere asegurarse de que lee correctamente los datos que fueron supuesto ser golpeado, por código se suponía que el compilador generaríaen caso de que no haya restrict Calificatorio. agregando restrict lo libera de esa preocupación.

Finalmente, tenga en cuenta que es probable que el compilador ya esté analizando posibles alias en función de los tipos de datos, en los niveles de optimización más altos, por lo que 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 deliberado que haga se realice 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 restrictel compilador no necesitaba volver a leer b[0] de memoria . fue capaz de leer b[0] y mantenerlo en el registro %edx, y luego simplemente almacene el registro dos veces en la memoria. En la columna de la izquierda, no sabía si la tienda a a puede haber cambiado b.

Alguien más familiarizado con el estándar probablemente podría dar una mejor respuesta, pero lo intentaré.

“Los datos no se modificarán a espaldas del compilador” me suena más a lo contrario de “volátil”.

“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 *piel nombre pi no es constante, pero *pi es). Los datos pueden modificarse a través de otro significante (después de todo, los datos que no son constantes se pueden pasar a una función como datos constantes).

Ese “restringir” califica punteros es la clave. Los punteros son la única forma de alias de datos en C, por lo que son la única forma en que puede 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.

avatar de usuario
marca rushakoff

Este podría ser un ejemplo de un extremadamente dominio 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 C para ese micro, puede usar una herramienta C-to-hardware para acelerar los bucles internos usando hardware personalizado, en lugar de software.

Allí, el uso de la __restrict__ palabra clave (que es la misma que la de C99 restrict) permite que la herramienta C2H optimice correctamente la aceleración de hardware de la operación del puntero en paralelo en lugar de secuencialmente. Al menos en este caso, la restrict es simple no destinados al consumo humano. Ver también página de Sun en restrictdonde dice la primera línea

Utilizando el restrict calificador apropiadamente en programas 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 analiza la optimización de los resultados de C2H. La sección sobre __restrict__ está en la página 20.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad