¿Pueden procesadores / relojes más rápidos ejecutar más código?

9

Estoy escribiendo un programa para ejecutar en un ATmega 328 que se ejecuta a 16Mhz (es un Arduino Duemilanove si los conoce, es un chip AVR).

Tengo un proceso de interrupción que se ejecuta cada 100 microsegundos. Es imposible, diría, calcular cuánto "código" puede ejecutar en un bucle de 100 microsegundos (estoy escribiendo en C, que presumiblemente se convierte en ensamblaje y luego en una imagen binaria).

Además, esto dependería de la complejidad del código (un trazador de líneas gigante podría correr más lento que varias líneas cortas, por ejemplo).

¿Entiendo bien que mi procesador con una velocidad de reloj o 16Mhz realiza 16 millones de ciclos por segundo (esto significa 16 ciclos por microsegundo 16,000,000 / 1,000 / 1,000); Entonces, si quiero hacer más en mi ciclo de 100 microsegundos, ¿comprar un modelo más rápido como una versión de 72Mhz me daría 72 ciclos por microsegundo (72,000,000 / 1,000 / 1,000)?

Actualmente funciona un poco demasiado lento, es decir, se tarda un poco más de 100 microsegundos en hacer el ciclo (cuánto tiempo es demasiado difícil de decir, pero se retrasa gradualmente) y me gustaría que hiciera un poco más, es ¿Es un enfoque sensato obtener un chip más rápido o me he vuelto loco?

jwbensley
fuente
.... Un ATmega328 NO es un chip ARM. Es un AVR.
vicatcu
Saludos, corregido!
jwbensley

Respuestas:

9

En general, el número de instrucciones de ensamblaje que el dispositivo puede ejecutar por segundo dependerá de la combinación de instrucciones y de cuántos ciclos tarda cada tipo de instrucción (CPI) en ejecutarse. En teoría, podría contar su código en ciclo mirando el archivo asm desmontado y mirando la función que le preocupa, contando todos los diferentes tipos de instrucciones en él y buscando los recuentos de ciclo de la hoja de datos para su procesador de destino.

El problema de determinar el número efectivo de instrucciones por segundo se exacerba en los procesadores más complejos por el hecho de que están canalizados y tienen cachés y qué no. Este no es el caso de un dispositivo simple como un ATMega328, que es una sola instrucción en el procesador de vuelo.

En cuanto a cuestiones prácticas, para un dispositivo simple como un AVR, mi respuesta sería más o menos "sí". Duplicar la velocidad de su reloj debería reducir la mitad del tiempo de ejecución de cualquier función. Sin embargo, para un AVR, no funcionarán más rápido que 20MHz, por lo que solo podría "overclockear" su Arduino en otros 4MHz.

Este consejo no se generaliza a un procesador que tenga características más avanzadas. Duplicar la velocidad del reloj en su procesador Intel no duplicará en la práctica el número de instrucciones que ejecuta por segundo (debido a predicciones erróneas de rama, errores de caché, etc.).

vicatcu
fuente
Hola, gracias por tu respuesta informativa! He visto uno de estos ( coolcomponents.co.uk/catalog/product_info.php?products_id=808 ), usted dijo que un AVR no puede ir más rápido que 20Mhz, ¿por qué es eso? El chip en la placa anterior ( uk.farnell.com/stmicroelectronics/stm32f103rbt6/… ) es un BRAZO de 72Mhz, ¿podría esperar un aumento de rendimiento razonable de la manera que describí anteriormente?
jwbensley
2
Duplicar la velocidad de procesamiento puede no aumentar el rendimiento de su instrucción, ya que puede comenzar a exceder la velocidad a la que se pueden obtener instrucciones desde el flash. En este punto, comienza a presionar "Estados de espera de flash" donde la CPU se detiene mientras espera que llegue la instrucción del flash. Algunos microcontroladores evitan esto al permitirle ejecutar código desde RAM, que es mucho más rápido que FLASH.
Majenko
@Majenko: divertido, ambos hicimos lo mismo al mismo tiempo.
Jason S
Sucede ... el tuyo es mejor que el mío :)
Majenko
1
OK, he marcado la respuesta de Vicatcu como "la respuesta". Siento que fue lo más apropiado con respecto a mi pregunta original de velocidad relacionada con el rendimiento, aunque todas las respuestas son geniales y estoy realmente satisfecho con las respuestas de todos. Me han demostrado que es un tema más amplio de lo que me di cuenta por primera vez, por lo que todos me están enseñando y dándome mucho para investigar, así que gracias a todos: D
jwbensley
8

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) % 4para 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) & 3por 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!

Jason S
fuente
buenos consejos sobre técnicas empíricas de medición del tiempo de ejecución
vicatcu
Otra gran respuesta para mi pregunta, ¡muchas gracias Jason S por esta increíble porción de conocimiento! Dos cosas aparentes después de leer esto; En primer lugar, puedo aumentar la interrupción de cada 100 uS a 500 uS para darle más tiempo al código para ejecutar, me doy cuenta de que ahora esto realmente no me beneficia ser tan rápido. En segundo lugar, creo que mi código puede ser demasiado ineficiente, con el mayor tiempo de interrupción y un mejor código, todo podría estar bien. Stackoverflow es un mejor lugar para publicar el código, así que lo publicaré allí y pondré un enlace aquí, si alguien quiere echar un vistazo y hacer alguna recomendación, por favor haga: D
jwbensley
5

Otra cosa a tener en cuenta: probablemente hay algunas optimizaciones que puede realizar para que su código sea más eficiente.

Por ejemplo, tengo una rutina que se ejecuta desde una interrupción del temporizador. La rutina tiene que completarse dentro de 52 µS, y debe pasar por una gran cantidad de memoria mientras lo hace.

Logré un gran aumento de velocidad al bloquear la variable principal del contador en un registro con (en mi µC y compilador, diferente al suyo):

register unsigned int pointer asm("W9");

No sé el formato para su compilador: RTFM, pero habrá algo que puede hacer para acelerar su rutina sin tener que cambiar al ensamblaje.

Dicho esto, probablemente pueda hacer un trabajo mucho mejor en la optimización de su rutina que el compilador, por lo que cambiar al ensamblaje puede darle algunos aumentos masivos de velocidad.

Majenko
fuente
jajaja "simultáneamente" comenté sobre mi propia respuesta sobre el ajuste del ensamblador y la asignación de registros :)
vicatcu
Si toma 100us en un procesador de 16 MHz, obviamente es bastante grande, por lo que es mucho código para optimizar. He oído que los compiladores de hoy producen aproximadamente 1.1 veces el código que el ensamblaje optimizado a mano. No vale la pena por una rutina tan grande. Para reducir un 20% de descuento en una función de 6 líneas, tal vez ...
DefenestrationDay
1
No necesariamente ... Podría ser solo 5 líneas de código en un bucle. Y no se trata del tamaño del código sino de la eficiencia del código . Es posible que pueda escribir el código de manera diferente para que se ejecute más rápido. Sé por mi rutina de interrupción que hice. Por ejemplo, sacrificando tamaño por velocidad. Al ejecutar el mismo código 10 veces en secuencia, ahorra el tiempo de tener el código para hacer el bucle y las variables de contador asociadas. Sí, el código es 10 veces más largo, pero se ejecuta más rápido.
Majenko
Hola Majenko, no conozco el ensamblaje, pero había estado pensando en aprenderlo y pensaba que el Arduino será menos complicado que mi computadora de escritorio, por lo que este podría ser un buen momento para aprender, especialmente porque quiero saber Más información sobre lo que está sucediendo y un nivel inferior. Como han dicho otros, no volvería a escribir todo solo ciertas partes. Entiendo que puedo entrar y salir del ASM dentro de C, ¿es correcto? ¿Es así como se puede lograr esta combinación de C y ASM? Publicaré en stackoverflow los detalles, justo después de una idea general.
jwbensley
@javano: sí. Puede entrar y salir de ASM dentro de C. Muchos sistemas embebidos se escribieron así, en una mezcla de C y ensamblaje , principalmente porque había algunas cosas que simplemente no se podían hacer en los compiladores de C primitivos disponibles en el hora. Sin embargo, los compiladores de C modernos como gcc (que es el compilador utilizado por Arduino) ahora manejan la mayoría y, en muchos casos, todas las cosas que solían requerir lenguaje ensamblador.
davidcary