¿Funciones de uso frecuente, mejoras de rendimiento utilizando variables estáticas?

¿Definir variables (matrices grandes) como estáticas dentro de una función tiene alguna mejora de rendimiento si la función se va a llamar repetidamente, digamos en el orden de segundos?

Ha etiquetado este "ensamblaje" pero habla de "estático". ¿Quiso decir en C en su lugar?
Esto podría depender de los modos de direccionamiento del procesador. Es posible que algo que pueda hacer que el direccionamiento relativo del puntero de pila en el hardware sea más rápido con variables automáticas. Algo que no puede o con un buen soporte de direccionamiento absoluto puede funcionar mejor con estático. El hecho de que esté utilizando una matriz complica aún más las cosas, ya que necesita encontrar la dirección base y luego acceder a un desplazamiento a partir de eso. Pero tenga en cuenta que las variables estáticas reclaman espacio todo el tiempo, no solo cuando se usan.
@IgnacioVazquez-Abrams: en realidad son ambas cosas. El término conceptual que enmarca la pregunta proviene de C, pero el detalle de implementación del que depende por completo es el lenguaje de máquina, generalmente expresado en mnemónicos ensambladores (en lugar de códigos de operación numéricos) para conveniencia humana.
Una de las cosas más útiles que podría hacer sería compilar ambas opciones y luego desensamblar el resultado para examinarlo.
¿Estoy siendo un poco demasiado entusiasta, o esta pregunta debería trasladarse a programmers.stackexchange o a stackoverflow? Es una pregunta interesante, solo que no para este ee.se. Además, hay muy poca información, creo que necesitamos saber cuál es el contexto (por ejemplo, servidor amd64 vs 8bit integrado), o la CPU (amd64, Pic16, ...). En mi humilde opinión, sin conocer la CPU o el contexto, la pregunta es demasiado vaga para ser respondida con mucho más que conjeturas.
Probablemente sería mejor obtener las hojas técnicas para su CPU en particular; generalmente le dicen cuántos ciclos de reloj toma cada instrucción en cada modo de direccionamiento disponible.

Respuestas (3)

En resumen: depende. En más cosas que quieras saber.

Algunas arquitecturas tienen instrucciones y/o modos de direccionamiento que acceden a datos relativos a la pila. Casi todas las arquitecturas tienen instrucciones y/o modos de direccionamiento que acceden a los datos en una dirección absoluta. Cuál es más rápido depende de la arquitectura particular.

Tenga en cuenta que algunas arquitecturas permiten solo un desplazamiento muy pequeño o una dirección (de página cero) en una instrucción, por lo que en una arquitectura de este tipo depende mucho de si el desplazamiento se ajusta. Cuando tiene muchas variables estáticas (= tiempo de vida global), es más probable que exceda una pequeña dirección de página cero (porque comparten el mismo espacio de direcciones absoluto) que con muchas variables automáticas (tiempo de vida local, desplazamiento de pila direccionado). ) variables (cada una de las cuales tiene su propio pequeño desplazamiento de SP).

Pero en las arquitecturas modernas de tipo PC, el acceso a la memoria (incluidos los problemas de caché) suele ser mucho más importante que la ejecución de instrucciones, especialmente cuando el código es un bucle cerrado y los datos están por todas partes. En tal caso, la velocidad de ejecución puede variar en décimas de % dependiendo exactamente de cómo se alinean los datos con respecto a varios problemas de caché (líneas, páginas, búferes, etc.), que se ve afectado por casi todo excepto por la fase de la luna, incluido otras aplicaciones que están o incluso han sido cargadas. Esto hace que la evaluación comparativa en tal situación sea muy dudosa.

¿Mejoras en comparación con qué? Supongo que tiene un programa C (o está usando terminología C para describir su programa) y desea comparar

  • (a) matriz grande declarada "const" en cualquier ámbito
  • (b) matriz grande declarada como estática dentro de una función (alcance de la función)
  • (c) matriz grande declarada como estática fuera de una función (ámbito de "archivo")
  • (d) matriz grande declarada fuera de una función (alcance global)
  • (e) matriz grande dentro de una función ("automático", alcance de la función) sin un inicializador
  • (f) matriz grande dentro de una función ("automático", alcance de la función) con un inicializador

(¿O hay algo más con lo que quieras comparar?)

automático con inicializador vs otros

La mayor diferencia de rendimiento está entre (f) y los demás: (f) copia el inicializador en la matriz y elimina a cero los elementos restantes, cada vez que llama a la función. El otro usa lo que sea que esté en la RAM en ese momento.

Si la función generalmente usa solo unos pocos elementos de la matriz, pero necesita una matriz muy grande para manejar los casos de esquina, entonces (f) perderá mucho tiempo escribiendo en cada byte de la matriz cada vez que lo llame, en comparación con los otros enfoques que omiten esa inicialización.

Por otro lado, a veces una función necesita que se restablezca una matriz modificable de cierta manera cada vez que la llama. Cuando elige (f), su compilador probablemente usa la forma más eficiente de hacerlo; es poco probable que cualquiera de los otros métodos (seguido de restablecer "manualmente" la matriz) sea más rápido.

automático frente a otros (relativo a la pila frente a absoluto)

Algunos compiladores para algunos procesadores usan exactamente la misma instrucción genérica de acceso a la matriz para cada tipo de matriz (a) a (f). Para esos compiladores, el tipo particular de matriz que elija no tiene efecto en el tiempo de ejecución.

A menudo, los procesadores obligan a un compilador a usar diferentes instrucciones para acceder a diferentes tipos de arreglos.

En procesadores con caché, las diferencias entre estas instrucciones son generalmente insignificantes.

Sin embargo, la mayoría de los procesadores no tienen caché, como los procesadores de 8 bits que venden más que todos los procesadores de 64 bits combinados. En esos procesadores, las diferencias en las instrucciones seleccionadas por el compilador pueden conducir a una diferencia notable en el tiempo de ejecución en un ciclo interno estrecho que accede a los elementos de la matriz sin muchos otros cálculos.

(Mi impresión es que las instrucciones relativas a la pila "automáticas" son las más rápidas en los microcontroladores ARM, mientras que las instrucciones "absolutas" para global y "archivo estático" y "función estática" son las más rápidas en los microcontroladores PIC16).

En general, será más rápido acceder a las variables que tienen direcciones conocidas en el momento de la compilación. Sin embargo, el beneficio exacto, si lo hay, depende de muchos otros factores. Por ejemplo, ¿cuál PIC? ¿Estático en oposición a qué tipo de asignación dinámica? ¿Qué tan grande es la matriz? Si está utilizando un compilador, esto puede depender de las estrategias elegidas por el diseñador del compilador.

Si está comparando variables estáticas (dirección conocida en el momento de la compilación) con variables de asignación dinámica en la pila de datos, entonces la estática será una victoria en un PIC 16 básico, ya que no tienen funciones de pila inherentes.

En un PIC 18, hay algunas capacidades de direccionamiento relativas limitadas fuera de los FSR, pero estas generalmente tomarán más ciclos que si se conoce una dirección fija. El desplazamiento de un registro de puntero es de solo 8 bits, por lo que el alcance también es limitado. Más allá de eso, debe hacer sus propios cálculos para calcular la dirección y cargarla en uno de los FSR.

En un PIC de 16 bits, hay más modos de direccionamiento relativos al registro, que permiten un acceso más flexible a los elementos de la pila. Sin embargo, hay aún más modos de direccionamiento disponibles si no tiene que pasar por un registro para obtener la dirección base. En algunos casos limitados, en realidad puede ser más rápido acceder a una variable de 16 bits en la pila, ya que la dirección de la pila ya está en W15, mientras que la dirección estática de una variable puede necesitar cargarse primero en un registro. La penalización por acceder a la memoria dinámica será menor en un PIC de 16 bits, pero creo que, en general, acceder a la memoria estática será más rápido. También hay una sección de memoria de datos, llamada "cerca", que permite instrucciones especiales que no están disponibles para acceder al resto de la memoria de datos. En algunos casos, si pudiera hacer arreglos para que la pila esté cerca de la memoria, pero eso no sería

Sin embargo, todo eso estaba considerando variables individuales. Está preguntando acerca de una gran matriz, lo que hace que las cosas vuelvan a ser diferentes. De todos modos, se accederá indirectamente a los elementos de una matriz con la dirección calculada en tiempo de ejecución, por lo que si la dirección de inicio de la matriz es una constante o algo que debe tomarse o calcularse, la diferencia en general es menor. En un PIC 18, el mayor problema es probablemente la disputa por el número limitado de FSR (registros de puntero de hardware), ya que solo hay 3 de ellos, y lo más probable es que al menos 1 ya esté asignado como puntero de pila de datos. (El compilador C18 es "menos que brillante" en la forma en que usa los punteros PIC 18 y, de hecho, se apodera de 2 de los 3 SFR para su propio uso). En un PIC de 16 bits, los ciclos de tiempo de ejecución serán aún menos diferentes.

Así que, básicamente, asigna las cosas de forma estática cuando puedas. Casi la única razón para no usar PIC es si necesita conservar memoria y sabe que cantidades significativas de memoria asignada dinámicamente nunca se asignarán simultáneamente. Por ejemplo, si dos subrutinas necesitan cada una una matriz de 2 kB solo para uso temporal, pero estas subrutinas nunca se llaman entre sí, la asignación dinámica de las matrices cuando sea necesario ahorra 2 kB en total en comparación con la asignación estática.

Otra razón, particularmente en un PIC 16 y PIC 18, es permitir que las ubicaciones especiales de memoria de acceso rápido se usen como borrador dentro de cada rutina. Por ejemplo, en muchos PIC 16, los últimos 16 bytes de cada banco acceden a los mismos 16 bytes globalmente. Estas ubicaciones de memoria limitada son, por lo tanto, bastante útiles como borrador temporal rápido porque se puede acceder a ellas independientemente de la configuración del banco. Mi convención es reservar la mayoría de estos como si fueran registros en otras arquitecturas de procesador. Por lo tanto, mis rutinas generalmente guardan las que desecharán en la pila de datos al entrar y las restaurarán al salir. El banco de acceso en un PIC 18 funciona de manera similar, aunque es más grande, por lo que también es más fácil de usar para otras cosas. Este concepto no se aplica realmente a los PIC de 16 bits porque tienen incorporados 16 registros de hardware reales.

Entonces, para recapitular, asigne estáticamente todo el estado persistente. Luego asigne estáticamente el estado restante a menos que necesite reducir el uso de memoria.