used - En C, ¿el uso de variables estáticas en una función lo hace más rápido?
volatile variable c (10)
Mi función será llamada 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, debido a que las variables estáticas son persistentes entre las llamadas de función, se asignan solo la primera vez y, por lo tanto, cada llamada subsiguiente no asignará memoria para ellas y se hará más rápida, porque el paso de asignación de memoria no se realiza.
Además, si lo anterior es cierto, ¿el uso de variables globales en lugar de parámetros sería más rápido 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 recursión consume más memoria), pero dado que mi función no es recursiva, y si mi razonamiento es correcto, en teoría, eliminar los parámetros hará que mas rapido
Sé que estas cosas que quiero hacer son hábitos de programación horribles, pero por favor, dígame si es prudente. Voy a intentarlo de todos modos, pero por favor dame tu opinión.
¡Absolutamente no! La única diferencia de "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 se llame a la función, en el segundo caso o se establecerá en 42 cuando se cargue el programa.
Sin embargo, la diferencia es tan trivial que apenas se nota. Es una idea errónea de que el almacenamiento debe asignarse para variables "automáticas" en cada llamada. Esto no es así por lo que C usa el espacio ya asignado en la pila para estas variables.
Las variables estáticas pueden ralentizarte, ya que sus optimizaciones agresivas no son posibles en las variables estáticas. Además, como los locales están en un área contigua de la pila, son más fáciles de almacenar en caché de manera eficiente.
El perfil puede no ver la diferencia, desmontar y saber qué buscar.
Sospecho que solo va a obtener una variación tanto como unos pocos ciclos de reloj por bucle (en promedio, dependiendo del compilador, etc.). A veces, el cambio será una mejora dramática o mucho más lento, y eso no necesariamente será porque las variables de inicio se han movido a / desde la pila. Digamos que guarda cuatro ciclos de reloj por llamada de función para 10000 llamadas en un procesador de 2ghz. Cálculo muy aproximado: 20 microsegundos guardados. ¿Son 20 microsegundos mucho o poco en comparación con el tiempo de ejecución actual?
Es probable que obtenga más mejoras en el rendimiento al hacer que todas sus características cortas y cortas se conviertan en caracteres, entre otras cosas. Es bueno saber que la microoptimización requiere mucho tiempo para experimentar, desensamblar, cronometrar la ejecución de su código, entender que menos instrucciones no necesariamente significa más rápido, por ejemplo.
Tome su programa específico, desmonte la función en cuestión y el código que lo 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 al perfilar. Los cambios en el lugar donde se encuentran las líneas de caché pueden aparecer en la creación de perfiles antes de los cambios en el código, por ejemplo.
El uso de variables estáticas puede hacer que su código sea significativamente más lento . Las variables estáticas deben existir en una región de "datos" de la memoria. Para usar esa variable, la función debe ejecutar una instrucción de carga para leer de la memoria principal, o una instrucción de almacenamiento para escribir en ella. Si esa región no está en el caché, perderá muchos ciclos. Una variable local que vive en la pila seguramente tendrá una dirección que está en el caché, e incluso podría estar en un registro de la CPU, que nunca aparecerá en la memoria.
Estoy de acuerdo con los otros comentarios sobre el perfil para descubrir cosas como esa, pero en general, las variables estáticas de funciones deberían ser más lentas. Si los quieres, lo que realmente buscas es un global. Las estadísticas de funciones insertan código / datos para verificar si la cosa ya se ha inicializado y se ejecuta cada vez que se llama a su función.
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 promediar los resultados y comparar, o puede considerar una herramienta de perfilado en toda regla que se adjunta a un proceso y grafica el uso de la memoria a lo largo del tiempo y la velocidad de ejecución.
No realice un ajuste de código micro aleatorio porque tiene la corazonada de que será más rápido. Todos 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 sobre menos parámetros: el proceso de funciones de "alineación" esencialmente elimina la sobrecarga relacionada con la llamada a una función. Las posibilidades son una pequeña función que se compondrá automáticamente por el compilador, pero también puede sugerir que una función esté en línea .
En un lenguaje diferente, C ++, el nuevo estándar que se presenta soporta el reenvío perfecto y la semántica de movimientos perfectos con referencias de valor que eliminan la necesidad de temporarios en ciertos casos, lo que puede reducir el costo de llamar a una función.
Sospecho que estás optimizando prematuramente, sin embargo, no deberías preocuparte tanto por el rendimiento hasta que hayas descubierto los verdaderos cuellos de botella.
La sobrecarga de las variables locales es cero. Cada vez que llama a una función, ya está configurando la pila para los parámetros, valores de retorno, etc. Agregar variables locales significa que está agregando un número un poco más grande al puntero de pila (un número que se calcula en el momento de la compilación) .
Además, las variables locales son probablemente más rápidas debido a la localidad de caché.
Si solo está llamando a su función "miles" de veces (no millones o billones), entonces debería estar buscando en su algoritmo oportunidades de optimización después de haber ejecutado un generador de perfiles.
Re: localidad de caché ( lea más aquí ): las variables globales a las que se accede con frecuencia tienen una localidad temporal. También pueden copiarse a un registro durante la ejecución de la función, pero se volverán a escribir en la memoria (caché) después de que la función regrese (de lo contrario, no serían accesibles a ninguna otra cosa; los registros no tienen direcciones).
Las variables locales generalmente tendrán una ubicación tanto temporal como espacial (obtienen eso por el hecho de que se crean en la pila). Además, pueden ser "asignados" directamente a los registros y nunca escribirse en la memoria.
No hay una respuesta para esto. Variará según la CPU, el compilador, los indicadores del compilador, el número de variables locales que tenga, lo que la CPU ha estado haciendo antes de llamar a la función y, posiblemente, la fase de la luna.
Considera dos extremos; Si solo tiene una o unas pocas variables locales, podrían almacenarse fácilmente en registros en lugar de asignarse a ubicaciones de memoria. Si el registro "presión" es lo suficientemente bajo como para que esto pueda ocurrir 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 consideramos como marcos de pila se asignan como una lista vinculada en el montón. Como probablemente adivinarías, 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 bien garantizado para ser más rápido de lo que cualquier cosa asignada en la memoria pueda esperar. OTOH, es posible que el acceso a las variables en la pila sea bastante lento; normalmente requiere algo como un acceso indirecto indexado, que (especialmente con las CPU más antiguas) tiende a ser bastante lento. OTOH, el acceso a un global (que es una estática, a pesar de que su nombre no es visible a nivel mundial) generalmente requiere la formación de una dirección absoluta, que algunas CPU también penalizan en cierta medida.
En pocas palabras: incluso el consejo para perfilar su código puede estar equivocado: la diferencia puede ser tan pequeña que incluso un generador de perfiles no lo detectará de manera confiable, y la única manera de asegurarse es examinar el lenguaje ensamblador que se produce (y pase unos años aprendiendo lenguaje ensamblador lo suficientemente bien como para saber decir algo cuando lo mire). 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 valen la pena.
Parece que la estática frente a la no estática se ha cubierto completamente, pero sobre 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 ámbito estricto facilitan que el compilador se optimice en gran medida, si el compilador tiene que mirar por toda la aplicación para detectar instancias en las que se podría usar un global, entonces su optimización no será tan buena.
Esto se complica cuando introduce punteros, digamos 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 nunca pueden superponerse y, por lo tanto, puede optimizar la copia. Si A y B son globales, entonces podrían apuntar a una memoria superpuesta o idéntica, esto significa que el compilador debe "jugar con seguridad", que es más lento. El problema generalmente se llama ''aliasing de punteros'' y puede ocurrir en muchas situaciones, no solo en copias de memoria.
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 que su programa sea multihilo. Como 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. Los subprocesos múltiples son el tipo de cosas que quizás desee hacer en el futuro para acelerar realmente su código.
La mayoría de las cosas que usted mencionó se conocen como micro-optimizaciones. En general, preocuparse por este tipo de cosas es una mala 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 obtengas más por tu dinero al hacer optimizaciones a un nivel superior.
Como sugerencias M2tM, ejecutar un perfilador también es una buena idea. Echa un vistazo a gprof para uno que es bastante fácil de usar.
Siempre puede cronometrar su aplicación para determinar verdaderamente qué es lo más rápido. 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 colocan los parámetros pasados, y se colocan las variables locales, así como el puntero de retorno al lugar donde el llamador llamó la función. No hay asignación de gestión de memoria aquí. Por lo general, un simple movimiento de puntero y eso es todo. Acceder a los datos de la pila también es bastante rápido. Las penalizaciones por lo general 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 se asignarán 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.