4 votos

Mi prueba de velocidad "flash VS RAM" no funciona. ¿Por qué?

Para intentar demostrar que copiar información de una cadena en la memoria flash a la RAM tarda más que copiar la misma información de una matriz en la RAM a otra, ejecuté el siguiente código en el PIC32 USB Starter Kit II (PIC32MX795F512L):

// Global variables:
char b[60000] = "Initialized";
char c[] = "Hello";

// In main():
// Version 1: Copying from flash to RAM:
    while(1){
        strcpy(b, "Hello");
         for (i = 0; i < 999; i++){
             strcat(b, "Hello");  
         }
         PORTD ^= 1; // Toggle LED

    }

// Version 2: Copying from RAM to RAM:
    while(1){
        strcpy(b, c);
         for (i = 0; i < 999; i++){
             strcat(b, c);  
         }
         PORTD ^= 1; // Toggle LED

    }

Esperaba ver el LED parpadeando más rápido en la versión 2, pero en cambio la versión 1 era mucho más rápida. ¿Cómo puede ser esto?

¿Podría ser que en lugar de copiar la información de la flash, estemos usando datos inmediatos codificados en el lenguaje de máquina MIPS? Tal vez debería tratar de entender el código MIPS.

Gracias de antemano.

2voto

Kiran Puntos 320

Hay varias explicaciones posibles muy diferentes. Aquí hay dos que se me ocurren:

  1. el reloj de la CPU funciona tan lentamente que la FLASH es tan rápida como la RAM, y como la Flash a la RAM utiliza dos buses independientes, en realidad tiene más ancho de banda.

EDIT: He mirado un Hoja de datos de Microchip para el PIC32MX5XX/6XX/7XX
En la Tabla 31-12: "CARACTERÍSTICAS DEL ESTADO DE ESPERA DE LA MEMORIA FLASH DE PROGRAMACIÓN", dice:

  • 0 Estado de espera 0 a 30 MHz
  • 1 Estado de espera 31 a 60 MHz
  • 2 Estados de espera de 61 a 80 MHz

Así que si el reloj de la CPU es de 30MHz o menos la memoria Flash puede seguir el ritmo sin esperas. No puedo encontrar ninguna especificación de tiempo para la SRAM, así que asumo que no tiene estados de espera a cualquier velocidad. Así que corriendo a 30MHz o menos Flash debería ser tan rápido como la SRAM.

Incluso por encima de esa velocidad de reloj de 30 MHz, los estados de espera de Flash pueden tener un impacto mucho menor de lo esperado debido a la "caché de preaprovisionamiento". Esta caché tiene 16 líneas de caché de 16 bytes. Por lo tanto, si el bucle del programa es inferior a 256 bytes (lo cual es factible para ese bucle), una vez que se carga la caché de preaprovisionamiento, todo el programa puede ejecutarse desde la caché de preaprovisionamiento, sin ningún otro acceso a Flash. Claramente esto beneficia a ambos bucles. Sin embargo, la versión 2 accede dos veces a la RAM para leer y escribir datos, mientras que la versión 1 sólo accede a la memoria para escribir en la RAM.

Teniendo en cuenta también la explicación 2, si el compilador también ha "optimizado" el strcat(b, "Hello"); haciendo que el bucle de la versión 1 sea más rápido que el de la versión 2, entonces el único acceso en la versión 1 a la memoria es almacenar bytes en b . Eso debería ser significativamente más rápido que copiar de RAM a RAM.

  1. El compilador optimiza la versión 1 como un loco. "Hola" es una constante. Incluso podría caber en dos registros de 32 bits, por lo que el compilador podría convertir la versión 1 en un muy un bucle estrecho. Estoy asumiendo que los relojes son correctos, y alguna versión de esto es la explicación.

La optimización de la versión 1 está especialmente bien hecha para los compiladores que tienen un conocimiento adecuado de strcpy y strcat. gcc tiene versiones internas de strcpy y strcat que puede elegir utilizar en las circunstancias apropiadas. También gcc optimiza strcpy y strcat para varios procesadores en diferentes secuencias), creo que incluso podría ampliar el strcat en línea en algunos casos, pero no puedo encontrar la referencia.

Así que deja el ensamblador y echa un vistazo. Para gcc ARM Cortex-M es arm-none-eabi-objdump. Esto volcará una versión textual de su programa, mostrando el ensamblador, normalmente organizado en funciones, y, si se utilizan las opciones correctas, puede entremezclar el código fuente C original como comentarios, haciendo relativamente fácil encontrar las instrucciones del ensamblador que corresponden a su código. (Aunque hay que tener en cuenta que, debido a la optimización, este mapeo puede no ser perfecto)

Si los datos, "Hola" en la versión 1, sólo se cargan en los registros y se escriben en la RAM en un bucle cerrado, entonces puede estar claro a partir de un volcado del ensamblador, incluso sin un conocimiento profundo del ensamblador MIPS.

¿Y si quieres hacer una comparación real entre flash y RAM?
Podrías dificultar la optimización al compilador, e intentar evitar que optimice las dos versiones del bucle de forma diferente.

Un enfoque sería forzar al compilador a almacenar el "Hola" en una variable que se fuerza en Flash.

No conozco el mecanismo para MIPS, pero es muy probable que haya un pragma o forma de pedir que una variable sea colocada en el segmento flash del programa por el enlazador.

Para gcc para ARM una variable puede ser 'decorada' con una anotación:
const uint8_t array[10] __attribute__((section(".eeprom"), used))
Esto marca la variable para que sea puesta en la sección ".eeprom" del enlazador, y el script de enlace del enlazador se asegura de que todas las direcciones para esa sección estén en el rango de direcciones de la memoria Flash).

Sin embargo, también puede ser necesario vencer las optimizaciones del compilador cuando se aplican a un valor de cadena constante.

Poner una versión de propósito general del bucle while en una función separada, myfunc(a *char, b *char) . Entonces llámalo con dos conjuntos diferentes de variables (RAM a RAM vs FLASH a RAM). Esto normalmente debería forzar al compilador a generar un conjunto de código (el cuerpo de myfunc), que se utilizará para ambos casos. Eso daría una comparación "manzanas por manzanas". Sin embargo, no subestimes la capacidad de optimización del compilador. Es posible que quieras volcar el ensamblador para comprobar que el compilador no se está pasando de listo.

(Yo pondría un límite en el número de iteraciones para evitar que garabatee en los periféricos)

Todo esto es una especulación. Necesitarás proporcionar más información, específicamente la inicialización del reloj de la CPU, los buses y los buffers, el compilador que estás usando, e idealmente un volcado del ensamblador, para dar respuestas más precisas.

Sin embargo, hacer el cambio a una sola función que ejecute un gran bucle for, y llamarla con dos conjuntos diferentes de parámetros podría ser suficiente para satisfacer su requisito

0 votos

Estoy impresionado por su utilidad y su profundidad de conocimientos. Muchas gracias. Veré qué puedo hacer con el código en lenguaje ensamblador. El problema que tengo con ese enfoque es que el IDE que estoy usando, el MPLAB X IDE, me muestra el código desensamblado sin etiquetas retenidas. Preferiría que el compilador generara directamente el código ensamblador, conservando las etiquetas. ¿Alguien sabe cómo hacerlo?

0 votos

@VititKantabutra - Me disculpo, pero no sé cómo hacer que MPLAB X IDE vuelque el ensamblador 'útil'. Estos enlaces podrían ayudar microchip.com/forums/m537589.aspx stackoverflow.com/questions/24914860/

0voto

Neil Foley Puntos 1313
  • Desactivar las optimizaciones.
  • Declarar todas las matrices como volátiles.

Hecho.

0 votos

He mirado un Hoja de datos de Microchip para el PIC32MX5XX/6XX/7XX En la Tabla 31-12: "PROGRAM FLASH MEMORY WAIT STATE CHARACTERISTIC", dice: - 0 Estado de espera de 0 a 30 MHz - 1 Estado de espera de 31 a 60 MHz - 2 Estados de espera de 61 a 80 MHz Por lo tanto, son estados de espera de Flash. En mi experiencia, las CPUs con "tasas de instrucción" superiores a 40MHz (ignora las CPUs multi-reloj/instrucción) hacer tienen estados de espera en la memoria Flash. Según parece, uno de los fabricantes japoneses de MCU (¿Toshiba, Fujitsu?) tiene una Flash mucho más rápida que la media de las MCU m/f.

0 votos

@gbulmer Ah, me perdí esa parte, gracias. Así que efectivamente puede haber un retraso para esta parte, dependiendo del reloj de la MCU. Sin embargo, creo que el método actual de evaluación comparativa del OP sería demasiado burdo para captar esos estados de espera.

0 votos

Sí, el manual es un poco incómodo en el sentido de que la sección de la memoria flash sólo menciona la programación de la flash, y el comportamiento de la flash es una tabla en una enorme sección de "características eléctricas". He sugerido una manera de vencer algunas de las optimizaciones de los compiladores, y asegurar que la mayor parte del bucle es el mismo código. Sin embargo, incluso eso podría mostrar que "Flash a RAM" es más rápido que "RAM a RAM" dependiendo de la secuencia de código MIPS real generada. En mi opinión, es útil reescribir el mismo código de bucle para cada caso, pero veamos el ensamblador; de lo contrario, como los banqueros, podríamos especular desastrosamente.

0voto

Asmyldof Puntos 9125

Su propio análisis es correcto.

La función strcpy(&, "") para cadenas pequeñas probablemente se optimizará a seis comandos LoadImmediate posteriores, a menos que la optimización sea completamente apagado que establecen un valor especificado en un byte (registro o de otro tipo, si está disponible). No optimizará más que eso, porque el tipo de fuente es char, que son bytes. Pero 6 LDI's sigue siendo más rápido que 6*2*LD. O si admite RAM directa a RAM podría hacerlo en 6*LD, pero como la opción 2 es más lenta, probablemente no.

Si quieres que la cadena provenga del espacio de datos de la Flash la única garantía para eso es averiguar cómo Microchip define un Array de la Flash, al igual que tus otras variables globales puedes decirle al sistema que debe crear un array de variables en la Flash.

La forma de configurarlo dependerá de tu entorno, debes buscar "Store Array in Flash PIC" seguido del nombre de tu compilador o entorno.

EDITAR:

Para evitar los comentarios interminables: Sí, cadenas largas codificadas puede se convierten en matrices flash (en algunas configuraciones de optimización), pero no hay reglas estrictas sobre lo que se considera "largo", universalmente. Así que me mantengo en mi afirmación de "la única garantía es ".

i-Ciencias.com

I-Ciencias es una comunidad de estudiantes y amantes de la ciencia en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X