Pregunta

Supongamos que tengo una función en un solo programa de rosca que tiene este aspecto

void f(some arguments){
    char buffer[32];
    some operations on buffer;
}

f y aparece dentro de alguna de bucle que es llamada a menudo, así que me gustaría que sea lo más rápido posible. A mi me parece como el buffer necesita conseguir asignado cada vez que f se llama, pero si declaro que sea estática, esto no ocurriría. Es que el razonamiento correcto? Es que una velocidad liberar? Y sólo porque de ese hecho (que es un sencillo de la velocidad hacia arriba), hace un compilador de optimización ya hacer algo como esto para mí?

¿Fue útil?

Solución

Para las implementaciones que utilizan una pila para las variables locales, a menudo asignación veces implica que avanzan un registro (la adición de un valor a la misma), tales como el puntero de pila (SP) registrar. Este tiempo es muy insignificante, por lo general una instrucción o menos.

Sin embargo, la inicialización de las variables de pila tarda un poco más, pero de nuevo, no mucho. Echa un vistazo a su lista de lenguaje ensamblador (generada por compilador o depurador) para los detalles exactos. No hay nada en la norma sobre la duración o el número de instrucciones necesarias para inicializar variables.

Asignación de variables locales estáticas generalmente se trata de manera diferente. Un enfoque común es colocar estas variables en la misma zona que las variables globales. Por lo general, todas las variables en esta área se inicializan antes de llamar main(). Asignación en este caso es una cuestión de asignar direcciones a los registros o almacenar la información de la zona en la memoria. No mucho tiempo de ejecución desperdicia aquí.

La asignación dinámica es el caso en el que se queman ciclos de ejecución. Pero eso no es en el ámbito de su pregunta.

Otros consejos

No, no es un aumento de velocidad libre.

En primer lugar, la asignación es casi libre, para empezar (ya que consiste simplemente en añadir 32 al puntero de pila), y en segundo lugar, hay al menos dos razones por las que una variable estática podría ser más lenta

  • se pierde localidad caché. Datos asignado en la pila va a estar en el caché de la CPU ya, así que el acceso es extremadamente barato. Los datos estáticos se asigna en un área diferente de la memoria, por lo que no puede ser almacenado en caché, por lo que causará un error de caché, y usted tendrá que esperar cientos de ciclos de reloj de los datos que se obtienen de la memoria principal.
  • se pierde la seguridad hilo. Si dos hilos se ejecutan simultáneamente la función, que va a estrellarse y arder, a menos que una cerradura se coloca de manera sólo se permite un hilo a la vez para ejecutar esa sección del código. Y eso significaría que perdería el beneficio de tener múltiples núcleos de CPU.

Así que no es una aceleración libre. Pero es posible que es más rápido en su caso (aunque lo dudo). Así que probarlo, punto de referencia, y ver lo que funciona mejor en su situación particular.

Incremento de 32 bytes en la pila costará prácticamente nada en casi todos los sistemas. Sin embargo, usted debe probarlo. Benchmark una versión estática y una versión local y volver después.

La forma en que se escribe ahora, no hay ningún costo para la asignación: los 32 bytes en la pila. El trabajo sólo es real es lo que necesita a cero-initialize.

estática local no es una buena idea aquí. No será más rápido, y su función no se puede utilizar de múltiples hilos más, ya que todas las llamadas comparten el mismo tampón. Por no hablar de que los estáticos locales inicialización no está garantizado al hilo.

Yo sugeriría que un enfoque más general a este problema es que entonces, si usted tiene una función llamada muchas veces que necesita un poco de tener en cuenta las variables locales de envolverlo en una clase y hacer estas funciones miembro variables. Considere si usted necesita para hacer la dinámica de tamaño, por lo que en lugar de char buffer[32] que tenía std::vector<char> buffer(requiredSize). Esto es más caro que una matriz para inicializar cada vez a través del bucle

class BufferMunger {
public:
   BufferMunger() {};
   void DoFunction(args);
private:
   char buffer[32];
};

BufferMunger m;
for (int i=0; i<1000; i++) {
   m.DoFunction(arg[i]);  // only one allocation of buffer
}

Hay otra implicación de hacer la estática tampón, que es que la función está ahora inseguro en una aplicación multiproceso, como dos hilos pueden llamar y sobrescribir los datos en la memoria intermedia al mismo tiempo. Por otro lado, es seguro de usar un BufferMunger separada en cada hilo que lo requiera.

Nota que las variables de nivel de bloque static en C ++ (en oposición a C) se inicializan en el primer uso. Esto implica que podrás introducir el costo de una verificación tiempo de ejecución adicional. La rama potencialmente podría terminar haciendo un rendimiento peor, no mejor. (Pero, en realidad, debería perfilar, como otros han mencionado.)

En cualquier caso, no creo que vale la pena, sobre todo porque serías intencionadamente sacrificar la reentrada.

Si está escribiendo código para un PC, no es improbable que sea ninguna ventaja significativa de velocidad en ambos sentidos. En algunos sistemas embebidos, puede ser ventajoso para evitar todas las variables locales. En algunos otros sistemas, las variables locales pueden ser más rápido.

Un ejemplo de lo anterior: en el Z80, el código para configurar el marco de pila para una función con cualquier variable local era bastante larga. Además, el código para acceder a las variables locales se limita al uso de la (IX + d) el modo, que sólo estaba disponible para las instrucciones de 8 bits de direccionamiento. Si X e Y son a la vez global / estática o ambas variables locales, la afirmación "X = Y" puede montar ya sea como:

; If both are static or global: 6 bytes; 32 cycles
  ld HL,(_Y) ; 16 cycles
  ld (_X),HL ; 16 cycles
; If both are local: 12 bytes; 56 cycles
  ld E,(IX+_Y)   ; 14 cycles
  ld D,(IX+_Y+1) ; 14 cycles
  ld (IX+_X),D   ; 14 cycles
  ld (IX+_X+1),E ; 14 cycles

Una pena espacio de código 100% y el 75% penalización de tiempo además del código y tiempo para establecer el marco de pila!

en el procesador ARM, una sola instrucción puede cargar una variable que se encuentra dentro de +/- 2K de un puntero de dirección. Si las variables locales de una función total de 2K o menos, que se puede acceder con una sola instrucción. Las variables globales requerirán generalmente dos o más instrucciones para cargar, dependiendo de dónde se almacenan.

Con gcc, noto cierta aceleración:

void f() {
    char buffer[4096];
}

int main() {
    int i;
    for (i = 0; i < 100000000; ++i) {
        f();
    }
}

Y el tiempo:

$ time ./a.out

real    0m0.453s
user    0m0.450s
sys  0m0.010s

cambio de tampón a la electricidad estática:

$ time ./a.out

real    0m0.352s
user    0m0.360s
sys  0m0.000s

En función de qué es exactamente la variable está haciendo y cómo su usado, la velocidad es casi nada a la nada. Debido a que (en sistemas x86) Memoria de pila se asigna a todos los VARs locales al mismo tiempo con una única y simple func (sub esp, cantidad), por lo tanto tener sólo una pila otros elimina cualquier ganancia var. La única excepción a esto es realmente enormes amortiguadores en cuyo caso un compilador podría pegarse en _chkstk a alloc memoria (pero si el búfer es tan grande que debe volver a evaluar su código). El compilador no puede girar memoria de pila en la memoria estática a través de la optimización, ya que no puede asumir que la función va a ser utilizado en un único entorno de rosca, además de que ensuciaría con objetos constructores y destructores, etc.

Si hay variables automáticas locales en la función del todo, el puntero de pila necesita ser ajustado. El tiempo necesario para que el ajuste es constante, y no variar en función del número de variables declaradas. Usted podría ahorrar algo de tiempo si su función se deja sin variables automáticas locales en absoluto.

Si se inicializa una variable estática, habrá una en algún indicador para determinar si la variable ya se ha inicializado. Comprobación de la bandera llevará algún tiempo. En su ejemplo la variable no se ha iniciado, por lo que esta parte puede ser ignorada.

Las variables estáticas deben ser evitados si su función tiene alguna posibilidad de ser llamado de forma recursiva o de dos hilos diferentes.

Esto hará que la función sustancialmente más lento en la mayoría de los casos reales. Esto se debe a que el segmento de datos estática no está cerca de la pila y se perderá coherencia de caché, lo que obtendrá un error de caché cuando intenta acceder a él. Sin embargo, cuando se puede asignar un char normal [32] en la pila, es justo al lado de todos sus datos y otros costes necesarios muy poco para el acceso. Los costes de inicialización de una matriz a base de pila de carbón tienen sentido.

Esto está haciendo caso omiso de que la estática tienen muchos otros problemas.

Lo que realmente necesitan para perfilar realidad su código y ver donde los retrasos son, porque ninguna de perfiles le dirá que la asignación de una memoria intermedia de forma estática de tamaño de los caracteres es un problema de rendimiento.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top