¿Usando restringir con matrices?

6 minutos de lectura

Avatar de usuario de Piotr Lopusiewicz
Piotr Lopusiewicz

¿Hay alguna manera de decirle a un compilador C99 que la única forma en que accederé a una matriz dada es usando myarray?[index] ? Di algo como esto:

int heavy_calcualtions(float* restrict range1, float* restrict range2)
{
    float __I promise I won't alias this__ tmpvalues[1000] = {0};

    ....
    heavy calculations using range1, range2 and tmpvalues;
    ....
}

Al usar restrict, prometí que no usaré alias range1 y range2, pero ¿cómo hago lo mismo para la matriz declarada dentro de mi función?

  • ¿Qué tipo de advertencia lanza?

    – dhein

    28 de septiembre de 2013 a las 8:56

  • “uso no válido de restricción”: se supone que debe usarse con punteros, no con matrices (hasta donde yo entiendo). Podría hacer float* restrict tmpvalues ​​= malloc(sizeof(float)*1000) pero entonces no estoy asignando en la pila, lo que también puede influir en el rendimiento. Además de decirle a un compilador que acceder a los índices de la matriz es seguro (por lo que no se necesitan lecturas defensivas) parece una extensión muy natural del uso restringido con punteros, por lo que intuitivamente tiene que haber una manera de hacerlo.

    –Piotr Lopusiewicz

    28/09/2013 a las 15:50


  • Lo hice: float* restrict tmpvalues ​​= alloca(sizeof(float)*1000); memset(tmpvalues, 0, sizeof(float)*1000); y es una mejora medible, pero preferiría hacerlo de forma estándar (como en la forma de queja C99)

    –Piotr Lopusiewicz

    28 de septiembre de 2013 a las 17:23

  • @PiotrLopusiewicz, ¿puedes publicar un código real donde float *restrict tmpvalues supera al VLA? (pref. con el código de tiempo ya establecido)

    –MM

    23 de mayo de 2014 a las 3:30

  • Acceda a la matriz con un puntero de restricción.

    –Jeff Hammond

    26 de abril de 2015 a las 1:03

Avatar de usuario de Harald
Harald

Aunque la respuesta de Jeff es correcta, es decir, siempre puedes hacer un puntero a la matriz asignada, el hecho es que el compilador sabe en tiempo de compilación, los valores de tmp no tendrán alias porque la variable se declara como una matriz real, no como un puntero. Las únicas posibilidades de alias de una matriz es declarar un puntero a ella, por lo que si no lo hace, no hay necesidad de declararlo como restrict. Esto es más evidente si tmpvalues es la única variable que tendrá dentro de la función.

El problema puede surgir si pasa el puntero a otra función, entonces allí debe indicar si el puntero recibido está restringido o no.

La documentación que encontré relacionada con este tema incluye el C99:

Sea D una declaración de un identificador ordinario que proporciona un medio para designar un objeto P como un puntero restringido al tipo T.

Tenga en cuenta que solo se aplica a los punteros.

Este otro documento de TI proporciona algunos consejos de ajuste de rendimiento utilizando el restrict palabra clave. Además de todos los consejos, la sección 3.3 proporciona ejemplos de cuándo es posible aplicar este calificador de tipo y cuándo no. Busca el x declaración de matriz en el medio de la página 16, establece que no declara un puntero y, por lo tanto, no puede ser restrict-calificado.

  • Si un puntero a la matriz se pasa al código externo, no hay una forma clara de indicar que el código externo no conservará una copia del puntero y usará esa copia para fines arbitrarios la próxima vez que se invoque el código externo. En mi humilde opinión, habría sido útil para C tener un calificador similar a restrict pero para usar con variables cuya dirección se ha tomado pero solo se usará de manera muy limitada, pero tal característica no está definida.

    – Super gato

    7 sep 2016 a las 23:14


  • @supercat un problema es: ¿cómo define esas ‘varias formas’? gcc tiene atributos (como ‘puro’) que se pueden adjuntar a funciones, lo que promete que la función no hará ciertas clases de cosas. Más allá de eso, ahora tenemos ‘optimización del tiempo de enlace’ donde el conjunto de herramientas del compilador básicamente puede ver lo que la función realmente está haciendo y usar esa información.

    – grego

    17 mayo 2017 a las 14:49

  • @greggo: Si estuviera escribiendo las reglas, diría que un registerEl objeto calificado solo puede tomar su dirección en la evaluación de un argumento de función, y el comportamiento solo se definiría en los casos en que todos los usos del puntero resultante o los punteros derivados del mismo ocurran antes de que la función regrese, y durante la ejecución de la función ( 1) se accede al objeto exclusivamente a través de un puntero resultante u otros derivados de él, o (2) no se modifica el objeto por ningún medio. Si un proceso de compilación comienza con la creación de una lista de todos los símbolos externos utilizados en cada función…

    – Super gato

    17 mayo 2017 a las 16:50

  • … un compilador podría asumir con seguridad que si ni “foo”, ni ninguna función llamada por “foo”, hace uso del símbolo “barra” asociado con un objeto calificado por registro, los accesos a “barra” pueden tratarse como relativos no secuenciados a cualquier cosa que ocurra dentro de la función [the definition of every function with a given signature would be regarded as a call from a generic function of that signature type to that function, and an indirect call to a pointer of a signature type would be a call to a generic function of that type, and thus a call to every function matching that signature].

    – Super gato

    17 mayo 2017 a las 16:54

Avatar de usuario de Jeff Hammond
jeff hammond

¿Por qué no puedes hacer lo siguiente? No estás accediendo a los datos asociados a tmpvalues a través de esa variable, por lo que es válido usar un puntero de restricción en la parte del código que requiere mucha computación.

#include <stdio.h>
#include <stdlib.h>

int heavy_calcs(int n, float* restrict range1, float* restrict range2)
{
    if (n>1000) return 1;
    float tmpvalues[1000] = {0};
    {
        float * restrict ptv = tmpvalues;
        for (int i=0; i<n; i++) {
            ptv[i] = range1[i] + range2[i];
        }
    }
    return 0;
}

int main(int argc, char * argv[])
{
    int n = (argc>1) ? atoi(argv[1]) : 1000;
    float * r1 = (float*)malloc(n*sizeof(float));
    float * r2 = (float*)malloc(n*sizeof(float));
    int rc = heavy_calcs(n,r1,r2);
    free(r1);
    free(r2);
    return rc;
}

Ejecuté esto a través del compilador Intel 15 y no tuve problemas para vectorizar el bucle. Por supuesto, este ciclo es trivial en comparación con lo que supongo que es el suyo, por lo que su millaje puede variar.

  • No es necesario lanzar el resultado de malloc Cía

    – phuclv

    25 de agosto de 2016 a las 7:58

  • No hay necesidad de la {} alrededor de ptv asignación tampoco.

    –Jeff Hammond

    29 de agosto de 2016 a las 3:54

¿Ha sido útil esta solución?