La respuesta de @vicatcu es bastante completa. Una cosa adicional a tener en cuenta es que la CPU puede encontrarse en estados de espera (ciclos de CPU detenidos) al acceder a E / S, incluidos los programas y la memoria de datos.
Por ejemplo, estamos usando un DSP TI F28335; Algunas áreas de la RAM tienen un estado de espera 0 para la memoria de programa y datos, por lo que cuando ejecuta el código en la RAM, se ejecuta a 1 ciclo por instrucción (a excepción de aquellas instrucciones que toman más de 1 ciclo). Sin embargo, cuando ejecuta código desde la memoria FLASH (EEPROM incorporada, más o menos), no puede ejecutarse a 150MHz completos y es varias veces más lento.
Con respecto al código de interrupción de alta velocidad, debe aprender varias cosas.
Primero, familiarícese con su compilador. Si el compilador hace un buen trabajo, no debería ser mucho más lento que el ensamblaje codificado a mano para la mayoría de las cosas. (donde "mucho más lento": un factor de 2 estaría bien para mí; un factor de 10 sería inaceptable) Debe aprender cómo (y cuándo) usar los indicadores de optimización del compilador, y de vez en cuando debe mirar en la salida del compilador para ver cómo funciona.
Algunas otras cosas que puede hacer que el compilador haga para acelerar el código:
use funciones en línea (no recuerdo si C lo admite o si es solo un isma C ++), tanto para funciones pequeñas como para funciones que se ejecutarán solo una o dos veces. La desventaja es que las funciones en línea son difíciles de depurar, especialmente si la optimización del compilador está activada. Pero le ahorran secuencias innecesarias de llamada / retorno, especialmente si la abstracción de "función" es para fines de diseño conceptual en lugar de implementación de código.
Consulte el manual de su compilador para ver si tiene funciones intrínsecas: estas son funciones integradas dependientes del compilador que se asignan directamente a las instrucciones de ensamblaje del procesador; algunos procesadores tienen instrucciones de ensamblaje que hacen cosas útiles como min / max / bit reverse y puede ahorrar tiempo al hacerlo.
Si está haciendo un cálculo numérico, asegúrese de no llamar innecesariamente a las funciones de la biblioteca matemática. Tuvimos un caso en el que el código era algo así como y = (y+1) % 4
para un contador que tenía un período de 4, esperando que el compilador implementara el módulo 4 como un bit-AND. En su lugar, llamó a la biblioteca de matemáticas. Así que reemplazamos y = (y+1) & 3
por hacer lo que queríamos.
Familiarízate con la página de trucos de bit bitiddling . Te garantizo que usarás al menos uno de estos a menudo.
También debe usar los periféricos del temporizador de su CPU para medir el tiempo de ejecución del código; la mayoría de ellos tienen un temporizador / contador que se puede configurar para que se ejecute a la frecuencia del reloj de la CPU. Capture una copia del contador al principio y al final de su código crítico, y podrá ver cuánto tarda. Si no puede hacer eso, otra alternativa es bajar un pin de salida al comienzo de su código, y subirlo al final, y mirar esta salida en un osciloscopio para cronometrar la ejecución. Hay compensaciones para cada enfoque: el temporizador / contador interno es más flexible (puede cronometrar varias cosas) pero es más difícil obtener la información, mientras que configurar / borrar un pin de salida es inmediatamente visible en un osciloscopio y puede capturar estadísticas, pero Es difícil distinguir múltiples eventos.
Finalmente, hay una habilidad muy importante que viene con la experiencia, tanto general como con combinaciones específicas de procesador / compilador: saber cuándo y cuándo no optimizar . En general, la respuesta es no optimizar. La cita de Donald Knuth se publica con frecuencia en StackOverflow (generalmente solo la última parte):
Deberíamos olvidarnos de las pequeñas eficiencias, digamos alrededor del 97% del tiempo: la optimización prematura es la raíz de todo mal
Pero se encuentra en una situación en la que sabe que tiene que hacer algún tipo de optimización, por lo que es hora de morder la bala y optimizar (u obtener un procesador más rápido, o ambos). No NO escribir toda la ISR en el montaje. Eso es casi un desastre garantizado: si lo hace, dentro de meses o incluso semanas olvidará partes de lo que hizo y por qué, y es probable que el código sea muy frágil y difícil de cambiar. Sin embargo, es probable que haya partes de su código que sean buenos candidatos para la asamblea.
Señales de que partes de su código son adecuadas para la codificación de ensamblaje:
- funciones que están bien contenidas, pequeñas rutinas bien definidas que es poco probable que cambien
- funciones que pueden utilizar instrucciones de ensamblaje específicas (min / max / desplazamiento a la derecha / etc.)
- funciones que se llaman muchas veces (le da un multiplicador: si ahorra 0.5usec en cada llamada, y se llama 10 veces, eso le ahorra 5 usec, lo cual es significativo en su caso)
Aprenda las convenciones de llamada de la función de su compilador (por ejemplo, dónde coloca los argumentos en los registros y qué registros guarda / restaura) para que pueda escribir rutinas de ensamblaje invocables en C.
En mi proyecto actual, tenemos una base de código bastante grande con código crítico que tiene que ejecutarse en una interrupción de 10 kHz (100usec, ¿suena familiar?) Y no hay tantas funciones escritas en ensamblador. Los que son, son cosas como el cálculo de CRC, las colas de software, la compensación de ganancia / compensación de ADC.
¡Buena suerte!