Um zu demonstrieren, dass das Kopieren von Informationen von einer Zeichenfolge im Flash-Speicher in den RAM länger dauert als das Kopieren derselben Informationen in einem Array im RAM in ein anderes, habe ich den folgenden Code auf dem PIC32 USB Starter Kit II (PIC32MX795F512L) ausgeführt:
// 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
}
Ich hatte erwartet, dass die LED in Version 2 schneller blinkt, aber stattdessen war Version 1 viel schneller! Wie konnte das sein?
Könnte es sein, dass wir, anstatt Informationen aus dem Flash zu kopieren, unmittelbare Daten verwenden, die in MIPS-Maschinensprache fest codiert sind? Vielleicht sollte ich versuchen, den MIPS-Code zu verstehen.
Vielen Dank im Voraus!
Es gibt mehrere sehr unterschiedliche mögliche Erklärungen. Da sind mir zwei eingefallen:
BEARBEITEN: Ich habe mir ein Microchip-Datenblatt für den PIC32MX5XX/6XX/7XX
angesehen . In Tabelle 31-12: "PROGRAM FLASH MEMORY WAIT STATE CHARACTERISTIC" heißt es:
Wenn also der CPU-Takt 30 MHz oder weniger beträgt, kann der Flash-Speicher ohne Wartezeiten mithalten. Ich kann keine Timing-Spezifikation für das SRAM finden, daher gehe ich davon aus, dass es bei keiner Geschwindigkeit Wartezustände gibt. Daher sollte Flash mit 30 MHz oder weniger so schnell sein wie SRAM.
Selbst oberhalb dieser 30-MHz-Taktrate könnten die Flash-Wartezustände aufgrund des „Prefetch-Cache“ einen viel geringeren Einfluss haben als erwartet. Dieser Cache hat 16 16-Byte-Cache-Zeilen. Wenn also die Programmschleife weniger als 256 Bytes groß ist (was für diese Schleife machbar ist), kann das gesamte Programm, sobald der Prefetch-Cache geladen ist, ohne weiteren Zugriff auf Flash aus dem Prefetch-Cache ausgeführt werden. Dies kommt eindeutig beiden Schleifen zugute. Version 2 greift jedoch zweimal auf RAM zu, um Daten zu lesen und zu schreiben, während Version 1 möglicherweise nur auf Speicher zugreift, um in RAM zu schreiben.
Unter Berücksichtigung von Erklärung 2, wenn der Compiler auch "optimiert" hat, strcat(b, "Hello");
die Version 1-Schleife schneller als die Version 2-Schleife zu machen, dann besteht der einzige Zugriff in Version 1 auf den Speicher darin, Bytes in zu speichern b
. Das sollte deutlich schneller sein, als von RAM zu RAM zu kopieren.
Die Optimierung von Version 1 ist besonders gut für Compiler geeignet, die über ausreichende Kenntnisse von strcpy und strcat verfügen. gcc hat intern eingebaute Versionen von strcpy und strcat , die es unter geeigneten Umständen verwenden kann. Außerdem optimiert gcc strcpy und strcat für mehrere Prozessoren in verschiedene Sequenzen), ich glaube, es könnte in einigen Fällen sogar strcat inline erweitern, kann aber die Referenz nicht finden.
Also den Assembler wegwerfen und nachsehen. Für gcc ARM Cortex-M ist es arm-none-eabi-objdump. Dadurch wird eine Textversion Ihres Programms ausgegeben, die den Assembler zeigt, normalerweise in Funktionen organisiert, und wenn die richtigen Optionen verwendet werden, kann es die ursprüngliche C-Quelle als Kommentare vermischen, wodurch es relativ einfach ist, die entsprechenden Assembler-Anweisungen zu finden dein Code. (Beachten Sie jedoch, dass diese Zuordnung aufgrund von Optimierungen möglicherweise nicht perfekt ist.)
Wenn die Daten "Hello" in Version 1 nur in Register geladen und in einer engen Schleife in den RAM geschrieben werden, kann dies aus einem Assembler-Dump auch ohne tiefgreifende Kenntnisse des MIPS-Assemblers ersichtlich sein.
Was ist, wenn Sie einen tatsächlichen Vergleich zwischen Flash und RAM durchführen möchten?
Sie könnten die Optimierung für den Compiler erschweren und versuchen zu verhindern, dass er die beiden Versionen der Schleife unterschiedlich optimiert.
Ein Ansatz wäre, den Compiler zu zwingen, das „Hallo“ in einer Variablen zu speichern, die Sie in Flash zwingen.
Ich kenne den Mechanismus für MIPS nicht, aber es gibt sehr wahrscheinlich ein Pragma oder eine Möglichkeit, nach einer Variablen zu fragen, die vom Linker in das Flash-Segment des Programms eingefügt werden soll.
Für gcc für ARM kann eine Variable mit einer Anmerkung „geschmückt“ werden:
const uint8_t array[10] __attribute__((section(".eeprom"), used))
Dadurch wird die Variable so markiert, dass sie in den Abschnitt „.eeprom“ des Linkers eingefügt wird, und das Linkskript des Linkers stellt sicher, dass sich alle Adressen für diesen Abschnitt im Flash-Speicher befinden Adressbereich).
Möglicherweise müssen Sie jedoch auch die Compileroptimierungen umgehen, wenn Sie sie auf einen konstanten Zeichenfolgenwert anwenden.
Fügen Sie eine Allzweckversion der While-Schleife in eine separate Funktion ein, myfunc(a *char, b *char)
. Rufen Sie es dann mit zwei verschiedenen Sätzen von Variablen auf (RAM zu RAM vs. FLASH zu RAM). Dies sollte den Compiler normalerweise dazu zwingen, einen Codesatz (den Hauptteil von myfunc) zu generieren, der für beide Fälle verwendet wird. Das würde einen „Äpfel für Äpfel“-Vergleich ergeben. Unterschätzen Sie jedoch nicht die Optimierungsfähigkeit eines Compilers. Vielleicht möchten Sie den Assembler trotzdem ausgeben, um zu überprüfen, ob der Compiler nicht zu schlau ist.
(Ich würde die Anzahl der Iterationen begrenzen, um zu verhindern, dass Peripheriegeräte gekritzelt werden.)
All dies ist Spekulation. Sie müssen weitere Informationen bereitstellen, insbesondere die Initialisierung der CPU-Uhr, Busse und Puffer, den von Ihnen verwendeten Compiler und idealerweise einen Assembler-Dump, um genauere Antworten zu geben.
Es könnte jedoch ausreichen, Ihre Anforderung zu erfüllen, wenn Sie die Änderung an einer einzelnen Funktion vornehmen, die eine große for-Schleife ausführt, und diese mit zwei verschiedenen Parametersätzen aufrufen
Deine eigene Analyse ist richtig.
Die Funktion strcpy(&, "") für kleine Zeichenfolgen wird höchstwahrscheinlich für sechs aufeinanderfolgende LoadImmediate-Befehle optimiert, es sei denn, die Optimierung ist vollständig ausgeschaltet , wodurch ein bestimmter Wert auf ein Byte gesetzt wird (Register oder anderweitig, falls verfügbar). Mehr wird nicht optimiert, da der Quelltyp char ist, was Bytes ist. Aber 6 LDIs sind immer noch schneller als 6 * 2 * LD. Oder wenn es direktes RAM zu RAM unterstützt, könnte es in 6 * LD funktionieren, aber da Option 2 langsamer ist, wahrscheinlich nicht.
Wenn Sie möchten, dass die Zeichenfolge aus dem Flash-Datenraum stammt, besteht die einzige Garantie dafür darin, herauszufinden, wie Microchip ein Flash-Array definiert. Genau wie Ihre anderen globalen Variablen können Sie dem System mitteilen, dass es ein Array von Variablen im Flash erstellen soll.
Wie Sie das einrichten, hängt von Ihrer Umgebung ab. Sie sollten nach „Store Array in Flash PIC“ suchen, gefolgt vom Namen Ihres Compilers oder Ihrer Umgebung.
Um endloses Kommentieren abzuwehren: Ja, lange hartcodierte Zeichenfolgen können zu Flash-Arrays werden (in einigen Optimierungseinstellungen), aber es gibt keine strengen Regeln dafür, was allgemein als "lang" gilt. Also bleibe ich bei meiner Aussage "Die einzige Garantie ist ...".
Erledigt.
for(;;) PORT ^= variable;
wenn "Variable" als deklariert ist volatile
und sich entweder im RAM oder im ROM befindet. Der Overhead-Code (die Schleife und das XOR) ist minimal und von statischer Länge, sodass Sie die Menge der durch den Overhead verursachten CPU-Ticks durch Disassemblieren leicht zählen können.for(;;) PORT ^= variable;
kann nutzlos sein. Es kann einen Engpass beim Zugriff auf den Peripheriebus geben und dem OP sehr wenig über RAM vs. Flash sagen. Außerdem könnte der „Prefetch-Cache“ unsichtbar sein, sodass der Wert selbst dann, wenn der Compiler Ladeanweisungen für die residente Flash-Variable generiert, aus dem Cache bereitgestellt wird und genauso schnell wie RAM ist. Assembler sagt uns vielleicht nicht genug
Vitit Kantabutra
gbulmer