Hay varias explicaciones posibles muy diferentes. Aquí hay dos que se me ocurren:
- 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.
- 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