19

Mi función se llamará miles de veces. Si quiero hacerlo más rápido, ¿será útil cambiar las variables de la función local a estática? Mi lógica detrás de esto es que, dado que las variables estáticas son persistentes entre las llamadas a funciones, se asignan solo la primera vez y, por lo tanto, cada llamada posterior no asignará memoria a ellas y se volverá más rápida, porque el paso de asignación de memoria no está hecho.En C, ¿el uso de variables estáticas en una función lo hace más rápido?

Además, si lo anterior es cierto, ¿sería más rápido usar las variables globales en lugar de los parámetros para pasar información a la función cada vez que se llama? Creo que también se asigna espacio para los parámetros en cada llamada de función, para permitir la recursión (es por eso que la recursividad consume más memoria), pero dado que mi función no es recursiva, y si mi razonamiento es correcto, en teoría, quitar los parámetros creará más rápido

Sé que estas cosas que quiero hacer son horribles hábitos de programación, pero por favor, dígame si es sabio. Voy a intentarlo de todos modos, pero por favor dame tu opinión.

+10

No optimice el código ANTES de la creación de perfiles! ... –

+1

http://stackoverflow.com/questions/3730000/can-static-local-variables-salida-de-la-memoria-alcance-hora – jamesdlin

+0

Hacer en general mal cosas para lograr aceleraciones apenas marginales para una función llamada miles de veces es una muy mala idea. Si puede ahorrar tal vez 10ns por llamada de funciones para una función llamada miles de veces ... ha guardado un múltiplo de 10 microsegundos, lo cual es trivial, a menos que esté trabajando en un sistema duro en tiempo real y tenga un problema crítico con el tiempo rebanando. –

Respuesta

22

La sobrecarga de las variables locales es cero. Cada vez que llamas a una función, ya estás configurando la pila para los parámetros, valores de retorno, etc. Agregar variables locales significa que estás agregando un número ligeramente mayor al puntero de la pila (un número que se calcula en tiempo de compilación) .

Además, las variables locales son probablemente más rápidas debido a la localidad de la memoria caché.

Si solo llama a su función "miles" de veces (no millones o miles de millones), entonces debe buscar su algoritmo para oportunidades de optimización después de ha ejecutado un generador de perfiles.


Re: localidad caché (read more here): variables globales se accede con frecuencia tienen probablemente localidad temporal. También pueden copiarse en un registro durante la ejecución de la función, pero se escribirán nuevamente en la memoria (caché) después de que una función regrese (de lo contrario, no podrían acceder a ninguna otra cosa, los registros no tienen direcciones).

Las variables locales generalmente tendrán tanto una localidad temporal como espacial (la obtienen en virtud de haber sido creadas en la pila). Además, se pueden "asignar" directamente a los registros y nunca se pueden escribir en la memoria.

+3

+1 en términos de velocidades de CPU modernas, "mil veces por segundo" es "una vez cada pocos millones de ciclos". –

+2

+1 aunque depende por supuesto de cómo el compilador genera el código. Para compiladores inteligentes. la diferencia es entre 'sub sp, 20' y' sub sp, 24', que no es ninguna diferencia. – paxdiablo

+0

+1: Estaba escribiendo casi la misma respuesta. – dawg

9

La mejor manera de averiguarlo es ejecutar un generador de perfiles. Esto puede ser tan simple como ejecutar varias pruebas cronometradas utilizando ambos métodos y luego promediando los resultados y comparando, o puede considerar una herramienta de creación de perfiles completa que se adhiera a un proceso y grafica el uso de la memoria a lo largo del tiempo y la velocidad de ejecución.

No realice ajustes aleatorios de micro códigos porque tiene la sensación de que será más rápido. Los compiladores tienen implementaciones de cosas ligeramente diferentes y lo que es cierto en un compilador en un entorno puede ser falso en otra configuración.

Para abordar ese comentario acerca de menos parámetros: el proceso de funciones "inline" esencialmente elimina la sobrecarga relacionada con llamar a una función. Lo más probable es que el compilador alinee automáticamente una pequeña función, pero también puede llamar al suggest a function be inlined.

En un idioma diferente, C++, el nuevo estándar admite un reenvío perfecto y una semántica de movimiento perfecta con referencias rvalue que elimina la necesidad de temporales en ciertos casos que pueden reducir el costo de llamar a una función.

Sospecho que estás optimizando prematuramente, sin embargo, no deberías preocuparte por el rendimiento hasta que descubras tus verdaderos cuellos de botella.

+0

+1 por ser sensato y resistir la tentación de aventurar una conjetura. :) –

+0

gracias! Lo intenté, como dije, lo haría. mi programa ya tenía un fragmento de código que contaba los segundos que tardó en hacerlo. lo que hizo en 60 segundos antes de lo estático/global ahora tomó 49 segundos. Todavía no puedo decir que fue una buena idea, pero parece que funcionó esta vez, dando resultados consistentes :) No sabía acerca de las optimizaciones del compilador o que la pila también se usaba para variables locales de funciones (todavía soy mucho de un novato). también, con seguridad veré C++ 0x cuando esté aquí (todas sus características: creo que la cosa rvalue y las lambdas ya están en GCC: D). ¡¡Gracias!! –

3

Absolutly not! La única diferencia "rendimiento" es cuando las variables se inicializan

int anint = 42; 
vs 
    static int anint = 42; 

En el primer caso el número entero se establecerá en 42 cada vez que la función se llama en el segundo caso ot será ajustado a 42 cuando el programa está cargado .

Sin embargo, la diferencia es tan trivial que apenas se nota. Es un concepto erróneo común que el almacenamiento debe asignarse para variables "automáticas" en cada llamada. Esto no es así, C usa el espacio ya asignado en la pila para estas variables.

Las variables estáticas en realidad pueden ralentizarlo ya que no es posible realizar algunas optimizaciones agresivas en variables estáticas. Además, como los lugareños se encuentran en un área contigua de la pila, es más fácil guardar en caché de manera eficiente.

1

Sí, el uso de variables estáticas hará que la función sea un poco más rápida. Sin embargo, esto causará problemas si alguna vez desea hacer que su programa tenga múltiples subprocesos. Dado que las variables estáticas se comparten entre invocaciones de función, invocar la función simultáneamente en diferentes subprocesos dará como resultado un comportamiento indefinido.Multi-threading es el tipo de cosas que puede querer hacer en el futuro para realmente acelerar su código.

La mayoría de las cosas que mencionas se conocen como micro-optimizaciones. En general, preocuparse por este tipo de cosas es un bad idea. Hace que su código sea más difícil de leer y más difícil de mantener. También es muy probable que introduzca errores. Es probable que obtenga más por su dinero haciendo optimizaciones en un nivel superior.

Como sugerencias M2tM, ejecutar un generador de perfiles también es una buena idea. Consulte gprof para obtener uno que sea bastante fácil de usar.

1

Siempre puede programar su aplicación para determinar realmente cuál es la más rápida. Esto es lo que entiendo: (todo esto depende de la arquitectura de su procesador, por cierto)

Las funciones C crean un marco de pila, que es donde se ponen los parámetros pasados, y se ponen las variables locales, así como el retorno puntero hacia donde la persona que llama llamó a la función. No hay asignación de administración de memoria aquí. Por lo general, un simple movimiento de puntero y eso es todo. El acceso a los datos de la pila también es bastante rápido. Las penalizaciones generalmente entran en juego cuando se trata de punteros.

En cuanto a las variables globales o estáticas, son las mismas ... desde el punto de vista de que van a asignarse en la misma región de memoria. El acceso a estos puede usar un método de acceso diferente al de las variables locales, depende del compilador.

La principal diferencia entre sus escenarios es la huella de memoria, no tanta velocidad.

+2

Este es un punto importante: siempre que sus variables no se inicialicen, asignar 100 variables automáticas es tan rápido como asignar una. – caf

+0

Cabe señalar que el compilador está "asignando" la memoria, no un sistema de gestión de memoria. – KFro

0

Estoy de acuerdo con los otros comentarios sobre la creación de perfiles para descubrir cosas así, pero en general, las variables estáticas de la función deberían ser más lentas. Si los quieres, lo que realmente buscas es un global. La función estática inserta código/datos para verificar si la cosa ya se ha inicializado y se ejecuta cada vez que se llama a su función.

1

El uso de variables estáticas realmente puede hacer que su código sea significativamente más lento. Las variables estáticas deben existir en una región de memoria de "datos".Para usar esa variable, la función debe ejecutar una instrucción de carga para leer desde la memoria principal, o una instrucción de tienda para escribir en ella. Si esa región no está en la memoria caché, perderá muchos ciclos. Una variable local que vive en la pila seguramente tendrá una dirección que está en la memoria caché, e incluso podría estar en un registro de la CPU, y nunca aparecerá en la memoria.

+0

* cada vez que se llama a la función, tiene que verificar para asegurarse de que la variable estática [no] se haya inicializado todavía * <- Esto es incorrecto. Antes de que main() se ejecute, todas las variables estáticas se inicializan (en __start()). Globales también se inicializan en este momento. –

+0

generalmente la instrucción de carga se usará tanto para el local en la pila como para el local en la región de datos. Obtener la variable inicializada la primera vez es un buen punto, una buena codificación requiere eso, si no es así. Saber si su compilador/entorno pone a cero esa memoria al iniciar el programa es un atajo a este estilo de codificación malo y arriesgado, pero a menudo funciona y es rápido (er). –

+0

@dwelch: el punto es que un local puede no aparecer en la memoria principal, puede optimizarse (de forma segura) para vivir solo en un registro. – SingleNegationElimination

3

No hay una respuesta a esto. Va a variar con la CPU, el compilador, los indicadores del compilador, el número de variables locales que tiene, lo que la CPU ha estado haciendo antes de llamar a la función y, muy posiblemente, la fase de la luna.

Considere dos extremos; si solo tiene una o algunas variables locales, podrían almacenarse fácilmente en registros en lugar de tener ubicaciones de memoria asignadas. Si la "presión" de registro es lo suficientemente baja como para que esto ocurra sin ejecutar ninguna instrucción en absoluto.

En el extremo opuesto hay algunas máquinas (por ejemplo, mainframes de IBM) que no tienen pilas en absoluto. En este caso, lo que normalmente consideraríamos como marcos de pila se asignan realmente como una lista vinculada en el montón. Como probablemente adivinaría, esto puede ser bastante lento.

Cuando se trata de acceder a las variables, la situación es algo similar: el acceso a un registro de máquina está bastante garantizado que es más rápido de lo que cualquier cosa asignada en memoria puede ser posible. OTOH, es posible que el acceso a las variables en la pila sea bastante lento; normalmente requiere algo así como un acceso indirecto indexado, que (especialmente con las CPU antiguas) tiende a ser bastante lento. OTOH, el acceso a un sistema global (que es estático, aunque su nombre no sea visible globalmente) generalmente requiere formar una dirección absoluta, lo que algunas CPU también penalizan en cierto grado.

En pocas palabras: incluso el asesoramiento a perfilar su código puede estar fuera de lugar - la diferencia puede ser fácilmente tan pequeña que incluso un generador de perfiles no lo detectará de forma fiable, y la única manera de estar seguro es examinar la ensamblar el idioma que se produce (y pasar algunos años aprendiendo el lenguaje ensamblador lo suficientemente bien como para saber decir algo cuando haga mírelo). La otra cara de esto es que cuando se trata de una diferencia que ni siquiera se puede medir de manera confiable, las posibilidades de que tenga un efecto material en la velocidad del código real son tan remotas que probablemente no valga la pena.

+0

Um, Jerry, aviso – Will

2

Parece que la estática vs no estática se ha cubierto por completo, pero en el tema de las variables globales. A menudo, esto ralentizará la ejecución de un programa en lugar de acelerarlo.

La razón es que las variables de amplio alcance facilitan al compilador optimizar mucho, si el compilador tiene que buscar en toda la aplicación las instancias en las que se pueda utilizar un global, su optimización no será tan buena.

Esto se agrava cuando se introduce punteros, supongamos que tiene el siguiente código:

int myFunction() 
{ 
    SomeStruct *A, *B; 
    FillOutSomeStruct(B); 
    memcpy(A, B, sizeof(A); 
    return A.result; 
} 

el compilador sabe que el puntero A y B no pueden superponerse y por lo que puede optimizar la copia. Si A y B son globales, posiblemente podrían indicar superposición o memoria idéntica, esto significa que el compilador debe 'jugar seguro', que es más lento. El problema generalmente se denomina 'alias de puntero' y puede ocurrir en muchas situaciones, no solo en las copias de memoria.

http://en.wikipedia.org/wiki/Pointer_alias

0

de perfiles no puede ver la diferencia, desmontaje y saber qué buscar fuerzas.

Sospecho que solo obtendrás una variación de hasta unos pocos ciclos de reloj por ciclo (en promedio dependiendo del compilador, etc.). Algunas veces el cambio será una mejora dramática o dramáticamente más lenta, y eso necesariamente será debido a que las variables de origen se han movido a/desde la pila. Digamos que ahorras cuatro ciclos de reloj por llamada de función para 10000 llamadas en un procesador de 2 ghz. Cálculo muy aproximado: 20 microsegundos guardados. ¿Son 20 microsegundos mucho o poco comparado con el tiempo de ejecución actual?

Es probable que obtenga una mayor mejora en el rendimiento al hacer todas sus variables cortas y cortas en ints, entre otras cosas. La micro optimización es algo bueno de saber, pero lleva mucho tiempo experimentar, desensamblar, sincronizar la ejecución de su código, entendiendo que pocas instrucciones no necesariamente significan más rápido, por ejemplo.

Tome su programa específico, desmonte tanto la función en cuestión como el código que la llama. Con y sin la estática. Si obtiene solo una o dos instrucciones y esta es la única optimización que va a hacer, probablemente no valga la pena. Es posible que no pueda ver la diferencia mientras crea perfiles. Los cambios en el lugar donde golpean las líneas de caché podrían aparecer en los perfiles antes de los cambios en el código, por ejemplo.

Cuestiones relacionadas