Forum: Mikrocontroller und Digitale Elektronik Arduino RAM sparen mit PROGMEM und strcpy_P()


von Ardur (Gast)


Lesenswert?

Im Internet habe ich viel zum Thema "Arduino RAM sparen mit PROGMEM und 
strcpy_P()" gefunden. Daher hier nur ganz konkrete Detailfragen, die 
eben nicht klar beantwortet werden. Zunächst zwei Code-Beispiele in C:
1
void doSomething( char fall ) {
2
  char command[45] = "Dies ist ein zu modifizierendes Textgerippe\n";
3
4
  // verändere jetzt etwas am String command, bevor er weiterverarbeitet wird
5
  ...
6
}

Nun die RAM-sparende Variante:
1
void doSomething( char fall ) {
2
  const static char Cmd[] PROGMEM = "Dies ist ein zu modifizierendes Textgerippe\n";
3
  char command[45];
4
  strcpy_P( command, Cmd );
5
6
  // verändere jetzt etwas am String command, bevor er weiterverarbeitet wird
7
  ...
8
}

Fragen dazu:

1) Stimmt es, dass beim 1. Programm der String vor main() zur Laufzeit 
ins Ram kopiert wird und dann beim Aufruf der Funktion doSomething() 
Platz auf dem Stack für command[] resierviert wird und zu Beginn der 
Funktion der String aus dem Ram an diesen Platz kopiert wird?

2) Stimmt es, dass beim 2. Programm beim Funktionsaufruf von 
doSomething() Platz auf dem Stack für command[] resierviert wird und 
dann nur mittels strcpy_P() direkt aus dem Flash in diesen Stack-Platz 
kopiert wird?

3) Wieviel länger braucht auf dem ATmega328 strcpy_P() als das Kopieren 
in 1)? 1 Taktzyklus pro Zeichen?

von Falk B. (falk)


Lesenswert?

Ardur schrieb:

> 1) Stimmt es, dass beim 1. Programm der String vor main() zur Laufzeit
> ins Ram kopiert wird und

Ja, denn das ist eine statische, lokale Variable.

> dann beim Aufruf der Funktion doSomething()
> Platz auf dem Stack für command[] resierviert wird und zu Beginn der
> Funktion der String aus dem Ram an diesen Platz kopiert wird?

Nein, denn die Variable ist statisch, sie ist schon dauerhaft angelegt, 
siehe oben.

> 2) Stimmt es, dass beim 2. Programm beim Funktionsaufruf von
> doSomething() Platz auf dem Stack für command[] resierviert wird und
> dann nur mittels strcpy_P() direkt aus dem Flash in diesen Stack-Platz
> kopiert wird?

Ja.

> 3) Wieviel länger braucht auf dem ATmega328 strcpy_P() als das Kopieren
> in 1)? 1 Taktzyklus pro Zeichen?

So in etwa. Aber 99,9% aller Programme stört das nicht, denn die CPU ist 
in den meisten Fällen mit anderen Sachen beschäftigt als zu 100% Strings 
zu kopieren.

von Stefan F. (Gast)


Lesenswert?

Falk B. schrieb:
> Nein, denn die Variable ist statisch, sie ist schon dauerhaft angelegt,
> siehe oben.

Er meint die Ziel Variable, in die er den String hinein kopiert. Die ist 
nicht statisch. Aber auch nicht auf dem Heap, sondern auf dem Stack.

von Falk B. (falk)


Lesenswert?

Stefan ⛄ F. schrieb:
> Er meint die Ziel Variable, in die er den String hinein kopiert. Die ist
> nicht statisch. Aber auch nicht auf dem Heap, sondern auf dem Stack.

Die gibt es im 1. Beispiel aber nicht!

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Falk B. schrieb:
> Die gibt es im 1. Beispiel aber nicht!

Doch:
1
char command[45] = "Dies ist ein zu modifizierendes Textgerippe\n";

command ist nicht statisch im ersten Programm, deshalb wird dieses Array 
bei jedem Aufruf von doSomething() neu gefüllt.

von Irgend W. (Firma: egal) (irgendwer)


Lesenswert?

Frank M. schrieb:
> Doch:char command[45] = "Dies ist ein zu modifizierendes Textgerippe\n";
>
> command ist nicht statisch im ersten Programm, deshalb wird dieses Array
> bei jedem Aufruf von doSomething() neu gefüllt.

Er sagt hier ja explizit das er "command" auch verändern können möchte 
und somit muss das im RAM stehen. Damit erstmal den ganzen Code zu 
durchsuchen das der Programmierer das hier eventuell falsch deklariert 
hat und er es eigentlich garnicht verändern will, soviel Esoterik ist 
nicht der Stiel von C/C++. Das geht öfters davon aus das der 
Programmierer weis was er will (und das auch richtig angeben kann:-).

folgender wäre zulässig:
1
char command[45] = "Dies ist ein zu modifizierendes Textgerippe\n";
2
strcpy(command, "Neuer Text");

Hier würde das ganze meckern:
1
const char command[] = "Dies ist ein zu modifizierendes Textgerippe\n";
2
strcpy(command, "Neuer Text");

Durch das const sagt der Programiere das die Variable in ihrem 
Gültigkeitsbereich nicht verändert werden darf und somit darf der 
Compiler/Linker das auch annehmen und entsprechende Optimierungen 
durchführen.

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

Ardur schrieb:
> Nun die RAM-sparende Variante:

Spart das wirklich was?
Was sagt denn avr-size dazu.

von Apollo M. (Firma: @home) (majortom)


Lesenswert?

... mich interessiert mal der Sinn von static hier, wenn die Variable 
doch sowieso "statisch" fix im Flash liegt.

Das erscheint mir reduntant/unnötig?! Sehe ich aber immer wiedermal ...

Ardur schrieb:
> const static char Cmd[] PROGMEM = "Dies ist ein zu modifizierendes
> Textgerippe\n";

von Falk B. (falk)


Lesenswert?

Peter D. schrieb:
> Ardur schrieb:
>> Nun die RAM-sparende Variante:
>
> Spart das wirklich was?

Es spart die (unsichtbare) RAM-Kopie! Denn der String wird beim 
Funktionsaufruf NICHT direkt aus dem Flash initialisiert sondern aus der 
Kopie im RAM. Eben darum sind konstante Strings auf dem AVR Mist 
(RAM-Verschwendung), welche man besser mit den _P Funktionen sowie 
PSTR("") Makro umgeht. Das sollte DIR aber als altem AVR-Hasen aber 
bekannt sein.

: Bearbeitet durch User
von batman (Gast)


Lesenswert?

Ack, so verdoppelt man sich schnell mal den freien RAM.

von Falk B. (falk)


Lesenswert?

Hier der Test für jeden nachvollziehbar. Test2 und 3 sind praktisch 
identisch, wenn gleich test3 kompakter ist.
1
#include <avr/io.h>
2
#include <avr/pgmspace.h>
3
4
#define TEST 1
5
6
#if TEST==1
7
void test1(void) {
8
    int i=0;
9
    char command[45] = "Hallo RAM-Verbrauchstest!";
10
11
    while(command[i]) {
12
        PORTD = command[i];
13
    }
14
}
15
#endif
16
17
#if TEST==2
18
void test2(void) {
19
    int i=0;
20
    char command[45];
21
    const static char cmd_init[45] PROGMEM= "Hallo RAM-Verbrauchstest!";
22
    
23
    strcpy_P(command, cmd_init);
24
    while(command[i]) {
25
        PORTD = command[i];
26
    }
27
}
28
#endif
29
30
#if TEST==3
31
void test3(void) {
32
    int i=0;
33
    char command[45];
34
    
35
    strcpy_P(command, PSTR("Hallo RAM-Verbrauchstest!"));
36
    while(command[i]) {
37
        PORTD = command[i];
38
    }
39
}
40
#endif
41
42
43
int main(void) {
44
#if TEST==1
45
   test1();
46
#endif
47
48
#if TEST==2
49
   test2();
50
#endif
51
52
#if TEST==3
53
    test3();
54
#endif
55
56
}
1
Test Flash RAM
2
1      270  46
3
2      242   0
4
3      222   0

von Ardur (Gast)


Lesenswert?

Dann ist also für meinen Fall, einen längeren String in einer Funktion 
verändern zu müssen, die von Falk B. (falk) vorgeschlagene 3. Variante 
die RAM- und Flash-sparendste:
1
void doSomething( char fall ) {
2
   char command[45];
3
   strcpy_P( command, PSTR("Dies ist ein zu modifizierendes Textgerippe\n") );
4
5
   // verändere jetzt etwas am String command, bevor er weiterverarbeitet wird
6
   ...
7
}

Ich bedanke mich ehrlich.

von Stefan F. (Gast)


Lesenswert?

Wenn du den modifizerten Text irgendwo seriell ausgibts (z.B. auf einem 
Display) könntest du ihn auch in mehreren Stücke zerlegen. Beispiel:

Statt: "Die Temperatur ist # Grad Celsius", wo dann # durch die Zahl 
ersetzt wird, könntest zu auch das tun:

ausgeben_P(PSTR("Die Temperatur ist "));
ausgeben(zahl);
ausgeben_P(PSTR(" Grad Celsius"));

Das würde noch viel weniger RAM belegen.

von MaWin (Gast)


Lesenswert?

Apollo M. schrieb:
> mich interessiert mal der Sinn von static hier, wenn die Variable doch
> sowieso "statisch" fix im Flash liegt

Wäre sie nicht static, könnte sie nicht im flash liegen, daher ist 
static nötig.

Falk B. schrieb:
> Das sollte DIR aber als altem AVR-Hasen aber bekannt sein.

Das schlimme ist, dass es dem GCC Compiler nicht bekannt ist. Der könnte 
optimieren, er weiss ob in Speicher hineingeschrieben wird, welche Kopie 
unnötig ist, der könnte selber eine vernünftige Speicheraufteilung 
erzeugen, aber er ist offenkundig noch immer zu doof dazu.

DAS ist das eigentlich Erschreckende im Jahr 2020.

Die ganzen Jubelperser "der beste Compiler, optimiert super, besser ist 
man per Hand in Assembler nie" müsstest du ansprechen.

(ich musste gerade den Takt eines AVR von 1 auf 8 MHz hochsetzen, damit 
er eine simple Interruptroutine noch schafft, die nicht häufiger als 
alle 50us aufgerufen wird:)
1
uint16_t buffer[256];
2
uint8_t pos;
3
ISR(TIMER1_CAPT_vect)
4
{
5
  buffer[pos++]=ICR1;
6
  TCCR1B^=(1<<ICES1);
7
}

Der Compiler ist damit überfordert.

von Stefan F. (Gast)


Lesenswert?

MaWin schrieb:
> Das schlimme ist, dass es dem GCC Compiler nicht bekannt ist. Der könnte
> optimieren, er weiss ob in Speicher hineingeschrieben wird, welche Kopie
> unnötig ist, der könnte selber eine vernünftige Speicheraufteilung
> erzeugen, aber er ist offenkundig noch immer zu doof dazu.

Der arm-gcc kann diesbezüglich einiges besser, als der avr-gcc. 
Vermutlich hat es damit zu tun, dass AVR spezielle Befehle für den 
Zugriff auf den Flash Speicher brauchen, die gängigen ARM Controller 
aber nicht.

von Einer K. (Gast)


Lesenswert?

Stefan ⛄ F. schrieb:
> Statt: "Die Temperatur ist # Grad Celsius", wo dann # durch die Zahl
> ersetzt wird, könntest zu auch das tun:
>
> ausgeben_P(PSTR("Die Temperatur ist "));
> ausgeben(zahl);
> ausgeben_P(PSTR(" Grad Celsius"));
1
#include <Streaming.h>
2
3
void setup() 
4
{
5
  Serial.begin(9600);
6
  Serial << F("Start: ") << __FILE__ << endl;
7
}
8
9
void loop() 
10
{
11
  float grad {47.11};
12
  Serial << F("Die Temperatur ist ") << grad << F(" Grad Celsius") << endl;
13
}

F() Makro:
https://www.arduino.cc/reference/de/language/variables/utilities/progmem/

von M. K. (sylaina)


Lesenswert?

MaWin schrieb:
> Das schlimme ist, dass es dem GCC Compiler nicht bekannt ist. Der könnte
> optimieren, er weiss ob in Speicher hineingeschrieben wird, welche Kopie
> unnötig ist, der könnte selber eine vernünftige Speicheraufteilung
> erzeugen, aber er ist offenkundig noch immer zu doof dazu.

Da das ja deiner Meinung nach so einfach zu implementieren ist: Na dann 
mal ran, MaWin. Muss ja ein Klacks für jemanden wie dich sein. ;)

von Oliver S. (oliverso)


Lesenswert?

MaWin schrieb:
> Der Compiler ist damit überfordert.

Kein Compiler dieser Welt ist foolproof. Wenn sich da der Anwender sich 
absichtlich dummer darstellt, als er ist, kann der Compiler nichts 
dafür.

Oliver

von Stefan F. (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:
> Arduino Beispiel

Ja, so macht man das "richtig" im Arduino Style. Hat den Vorteil, dass 
es genau so auch auf ARM Controllern funktioniert.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.