[C++/Arduino] Efficiënter omgaan met geheugen?

Pagina: 1
Acties:

Acties:
  • 0 Henk 'm!

  • ikt
  • Registratie: Juli 2008
  • Laatst online: 08-10 21:53
Hoi :)

Ik ben bezig met het programmeren van mijn Arduino Pro Micro-aangestuurd keypad, waar ik meerdere lagen en macros op wil hebben. Dat is voor een groot deel gelukt. Ik heb echter mijn twijfels over hoe ik met het geheugen om ga, maar ik zie geen andere manier om dit efficiënter te doen.

Broncode

KeyInfo.h
Elke KeyInfo heeft een type. Ik heb er hier 4 van: KeyCode (exclusief normale toets), Layer (exclusief om tussen layers te wisselen), Dual (combinatie van de vorige), Macro (exclusief om een macro te starten).

Macro.h
Een Macro heeft een array aan keystrokes, elke keystroke heeft een begintijd en eindtijd.

keypad.ino
De hoofd-schets. Ik maak voor elke KeyInfo variant die ik gebruik in de layers een object. Een layer is gedefiniëerd als een twee-dimensionale array van pointers naar KeyInfos. Aan de hand van wat toetsen kan ik wisselen tussen layers, door de actieve layer-pointer te laten wijzen naar één van de bestaande layers.

Met het huidige programma zit ik al op 39% van het geheugen. Dit zijn 3 normale layers met bijna elke toets een eigen KeyInfo, en 1 macro-laag met een CTRL-ALT-DEL macro en een "Hello, World!" macro.

code:
1
2
Program size: 9,254 bytes (used 32% of a 28,672 byte maximum) (1.02 secs)
Minimum Memory Usage: 994 bytes (39% of a 2560 byte maximum)


Vooral de macro hakt erin. De Hello World (13 strokes, 1 Macro, 1 KeyInfo) is al 139 bytes groot.

Kan dit slimmer? Of verwacht ik te veel van mijn Pro Micro?

Acties:
  • +1 Henk 'm!

  • Feanathiel
  • Registratie: Juni 2007
  • Niet online

Feanathiel

Cup<Coffee>

Nog even los van de inhoud: Hoeveel bytes gebruiken de dependencies nu (Keyboard.h) en hoeveel verwacht je te willen gaan besparen?

Acties:
  • 0 Henk 'm!

  • ikt
  • Registratie: Juli 2008
  • Laatst online: 08-10 21:53
Feanathiel schreef op maandag 7 augustus 2017 @ 19:00:
Nog even los van de inhoud: Hoeveel bytes gebruiken de dependencies nu (Keyboard.h) en hoeveel verwacht je te willen gaan besparen?
Het programma waarop ik het heb gebaseerd (1 layer) gebruikt 364 bytes.

code:
1
2
Program size: 6,392 bytes (used 22% of a 28,672 byte maximum) (0.82 secs)
Minimum Memory Usage: 364 bytes (14% of a 2560 byte maximum)


Een sketch die enkel Keyboard initialiseert gebruikt 224 bytes.

Ik hoop te besparen op de initialisatie van de KeyInfo objecten (6 bytes per KeyInfo, enkel aanmaken wat ik nodig heb?) en vooral macros.

Acties:
  • +1 Henk 'm!

  • DroogKloot
  • Registratie: Februari 2001
  • Niet online

DroogKloot

depenisvanjezus

Je hebt maar twee macro's vziz en zult er nooit honderden gaan gebruiken, dus KeyInfo kan makkelijk compacter: vervang de m_macro pointer door een uint8_t array-index voor 3 bytes gratis winst per KI. Dit

C:
1
2
3
4
5
Macro testMacro(13, helloWorldStrokes);
KeyInfo M_HelloWorld(&testMacro);

Macro ctrlAltDelMacro(3, ctrlAltDelStrokes);
KeyInfo M_CTRLALTDEL(&ctrlAltDelMacro);


verandert dan in

C:
1
2
3
Macro macros[] = {Macro(13, helloWorldStrokes), Macro(3, ctrlAltDelStrokes)};
KeyInfo M_HelloWorld(KeyType::Macro, 0, -1, 0);
KeyInfo M_CTRLALTDEL(KeyType::Macro, 0, -1, 1);


waarbij je uiteraard wel even de constructors en GetMacro moet aanpassen.

Met enum KeyType: uint8_t kun je trouwens ook op de enum besparen als de compiler dat niet al regelt.

[ Voor 8% gewijzigd door DroogKloot op 07-08-2017 20:44 ]


Acties:
  • 0 Henk 'm!

  • ikt
  • Registratie: Juli 2008
  • Laatst online: 08-10 21:53
Thanks! Het enum type op uint8_t zetten bespaart inderdaad een byte per KeyInfo.
994->942 bytes

KeyInfo houdt ipv een pointer een int8_t bij, maar ik bespaar hier maar een byte per KeyInfo ipv 3.
942->890 bytes

Blijkbaar zijn pointers dus 16-bit. Totaal dus 104 bytes bespaard, bedankt! :)

Ik denk dat ik beter over kan stappen op het schrijven van een functie die een layer maakt op de heap, dat zal dan 20 pointers per laag besparen. Dan nog vind ik die globale KeyInfo's lelijk, maar nu heb ik nog geen idee wat daar een mooie oplossing is. Ideaal gezien zou ik die meteen bij het maken van een laag kunnen maken, maar dan is de overzicht weg.

Macros dan maar? Dan wordt 't wel een zooitje om te onderhouden :/

Update
Och jee toch.

890 bytes -> 862 bytes bij dynamische allocatie. Maar 28 bytes vrijgemaakt :( Ik heb natuurlijk nog die 52 KeyInfo's staan. Nog steeds raar dat 4+4x20 pointers daar niet in terugkomen. En mooier is het ook niet geworden.
https://github.com/E66666...ce-sram/keypad/keypad.ino

Update
Oké... Macros ook dynamisch, dat scheelt wat. 166 bytes maar liefst.

Minimum Memory Usage: 696 bytes (27% of a 2560 byte maximum)

Ik zit me toch een beetje af te vragen of dit niet overdreven is, maar ja, zonder alles kom ik al snel over de 50% van het SRAM heen.

Het toevoegen van 1 macro kost nu maar 4 bytes. Een heel riedeltje "Win+R, https://tweakers.net, return" kost bijna niks nu.

[ Voor 34% gewijzigd door ikt op 08-08-2017 00:43 ]


Acties:
  • +1 Henk 'm!

  • ThomasG
  • Registratie: Juni 2006
  • Laatst online: 23-09 14:00
Je kunt je Stroke natuurlijk nog compacter maken. Je zou de keyUp en keyDown kunnen vervangen door een incremental uint8_t, en als je genoegen neemt een maximale interval - ten opzichte van de voorganger - van 127, kun je de active boolean daar ook nog in stoppen (met bit masking). Of beter, je haalt de active uit Stroke en maakt een uint8_t array aan bij het uitvoeren van de macro met een element voor elke 8 strokes, en dat masken. Als er maar een enkele stroke actief kan zijn, kun je dat ook met een enkele uint8_t doen voor de index van de stroke.

Acties:
  • 0 Henk 'm!

  • ikt
  • Registratie: Juli 2008
  • Laatst online: 08-10 21:53
ThomasG schreef op dinsdag 8 augustus 2017 @ 10:53:
Je kunt je Stroke natuurlijk nog compacter maken. Je zou de keyUp en keyDown kunnen vervangen door een incremental uint8_t, en als je genoegen neemt een maximale interval - ten opzichte van de voorganger - van 127, kun je de active boolean daar ook nog in stoppen (met bit masking). Of beter, je haalt de active uit Stroke en maakt een uint8_t array aan bij het uitvoeren van de macro met een element voor elke 8 strokes, en dat masken. Als er maar een enkele stroke actief kan zijn, kun je dat ook met een enkele uint8_t doen voor de index van de stroke.
Dat laatste heb ik geprobeerd, maar het levert niet zo erg veel op. Ik heb nu (bijna alles) dynamisch gealloceerd, zodat het alleen bestaat wanneer het wordt gebruikt. Ook nog de StandardCplusplus gevonden die STL-containers mogelijk maakt. Mijn programma is wel flink gegroeid, maar het versimpelt wel het geheugenbeheer.

Het maximale interval zal jammer genoeg niet werken, aangezien je dan geen meerdere toetsen tegelijk kunt activeren ;)

Acties:
  • +1 Henk 'm!

  • epic007
  • Registratie: Februari 2004
  • Laatst online: 07-10 10:46
Wellicht de m_switchId en de m_macro van KeyInfo nog in een union ? Je gebruikt toch maar een van de twee.

C++:
1
2
    int8_t m_switchId;
    Macro *m_macro = nullptr;


Of een subclass natuurlijk KeyInfoWithMacro ;)

[ Voor 11% gewijzigd door epic007 op 08-08-2017 15:23 ]


Acties:
  • +1 Henk 'm!

  • janwillemCA
  • Registratie: Mei 2014
  • Laatst online: 26-09 09:41
Heb je al eens overwogen om je arduino te programmeren in plat C?

https://balau82.wordpress...ng-arduino-uno-in-pure-c/

Dit scheelt soms aanzienlijk in efficientie!

Zelf schrijf ik vaak eerst even een snelle sketch in het Arduino C achtige taaltje, om het erna netjes in C uit te schrijven wat na compileren vaak toch kleiner is!

Unix is simple. It just takes a genius to understand its simplicity


Acties:
  • 0 Henk 'm!

  • ikt
  • Registratie: Juli 2008
  • Laatst online: 08-10 21:53
@epic007
Oh ja, goed idee om onnodige velden te squashen.

@janwillemCA
Ik ben juist de compleet andere kant opgegaan. De reden dat ik in het begin alles als globale variabelen had staan was juist omdat arrays als argumenten in C niet zo heel lekker gaan. (En slechte planning...) Gelukkig doen de STL containers zelf memory management, dus dit is ook geen probleem meer. Naar mijn weten is er weinig verschil tussen het returnen van een STL-container en het zelf alloceren/de-alloceren van variabelen (die toevallig arrays zijn).

Acties:
  • 0 Henk 'm!

  • EddoH
  • Registratie: Maart 2009
  • Niet online

EddoH

Backpfeifengesicht

edit: - nvm, niet wakker, ging om RAM usage, geen ROM usage -

[ Voor 74% gewijzigd door EddoH op 09-08-2017 11:40 ]


  • Lone Gunman
  • Registratie: Juni 1999
  • Niet online
Je moet er eigenlijk voor zorgen dat je layers en macros alleen in program memory/flash/rom komen te staan. Uit je code blijkt iig dat er op dit moment geen reden om deze in je ram te hebben (tenzij je dynamisch programmeerbare macro's gaat toevoegen oid). Heb zelf helaas weinig ervaring met arduino's, maar in het algemeen gebruik je daarvoor de const modifier en soms nog wat platformspecifieke keywords/functies. Als je dit goed voor elkaar krijgt heb je bijna geen ram meer nodig (gokje: < 50 bytes).

Experience has taught me that interest begets expectation, and expectation begets disappointment, so the key to avoiding disappointment is to avoid interest.


  • farlane
  • Registratie: Maart 2000
  • Laatst online: 27-09 13:03
Je kunt inderdaad al je KeyInfo's
C:
1
const static
maken waardoor ze in ROM terecht zoudem moeten komen.

[ Voor 6% gewijzigd door farlane op 10-08-2017 14:11 ]

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


Acties:
  • +1 Henk 'm!

  • Sissors
  • Registratie: Mei 2005
  • Niet online
Arduinos hebben daarbij nog een PROGMEM nodig: https://www.arduino.cc/en/Reference/PROGMEM

[ Voor 22% gewijzigd door Sissors op 11-08-2017 08:07 ]


Acties:
  • 0 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 27-09 13:03

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


Acties:
  • 0 Henk 'm!

  • ikt
  • Registratie: Juli 2008
  • Laatst online: 08-10 21:53
Ik heb alles ondertussen met behulp van de stdlib dynamisch kunnen teruggeven, dus het RAM-verbruik is voldoende teruggedrongen. Ondertussen heb ik nog een paar lange macro's toegevoegd zonder dat er problem bij kwamen kijken - die bestaan ook alleen als ze worden aangeroepen :)

Acties:
  • 0 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 27-09 13:03
ikt schreef op zondag 13 augustus 2017 @ 11:59:
Ik heb alles ondertussen met behulp van de stdlib dynamisch kunnen teruggeven, dus het RAM-verbruik is voldoende teruggedrongen.
Hoezo zou dynamisch memory (ik neem aan dat je daarmee het gebruik van heap memory bedoelt) gebruik het geheugengebruik terug dringen? Of was een gedeelte van het gebruikte geheugen een temporary (in dat geval kun je ook een variable length array gebruiken) ? Of was er een stuk heap gereserveerd die je niet gebruikte en daardoor had je te weinig voor de rest?

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


Acties:
  • 0 Henk 'm!

  • ThomasG
  • Registratie: Juni 2006
  • Laatst online: 23-09 14:00
farlane schreef op donderdag 10 augustus 2017 @ 14:10:
Je kunt inderdaad al je KeyInfo's
C:
1
const static
maken waardoor ze in ROM terecht zoudem moeten komen.
Dat mag je niet vanuit gaan, omdat een const helemaal niet geoptimaliseerd kan/mag worden door de compiler. Je kunt namelijk via een cast de const (per ongeluk) negeren. Een compiler kan een variabel die toevallig const heeft optimaliseren, maar dat komt niet doordat het const is; de compiler heeft dan bepaald dat het veilig is en voordeel oplevert. Om het (gegarandeerd) in speciale registers/geheugen te krijgen, moet je vaak iets extra's doen.

Acties:
  • 0 Henk 'm!

  • Sissors
  • Registratie: Mei 2005
  • Niet online
Des te meer reden dat een compiler het in het flash zou moeten gooien, dan kan je ook niet via een cast je const wijzigen ;).

ARM compilers waarmee ik heb gewerkt zetten alles wat const is netjes in het flash geheugen. Maar inderdaad, het kan wel opletten zijn of je eigen compiler ook zo werkt. (En dat is dan puur omdat hij als const is gedefinieerd, identieke code zonder const komt het gewoon in het SRAM te staan).

(Sowieso eens nagezocht wat er gebeurd als je door te casten een const waarde overschrijft, maar dat is niet bepaald aan te raden, gezien de compiler ook kan hebben gekozen om die const te inlinen in de binary, die kan toch niet veranderen volgens zijn definitie).

Acties:
  • +1 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 08-10 20:31

.oisyn

Moderator Devschuur®

Demotivational Speaker

ThomasG schreef op maandag 14 augustus 2017 @ 09:12:
[...]
Dat mag je niet vanuit gaan, omdat een const helemaal niet geoptimaliseerd kan/mag worden door de compiler. Je kunt namelijk via een cast de const (per ongeluk) negeren.
Nonsens :). Een const_cast op een als const gedefinieerd object geeft undefined behaviour. Als je de const ergens per ongeluk wegcast dan is dat volledig je eigen schuld. Const objecten mogen dan ook prima in ROM terecht komen.

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

Verwijderd

Je Stroke type is vrij log, nu heb je
code:
1
2
3
4
    uint8_t Key;
    unsigned long KeyDown;
    unsigned long Duration;
    bool Active = false;

Ik weet niet precies hoe de optimizer van de ATmega werkt, maar meestal moet een type gealigned zijn op zijn grootte. De betekent dat een long dus on n*sizeof(long) in het geheugen moet staan. Je Key staat ervoor, dus moet die opgerekt worden naar ook sizeof(long). Als je Key naar ná Duration verplaatst zou je zomaar een paar bytes per Stroke kunnen winnen.

Daarnaast: Je gebruikt een vector om je Strokes in te bewaren, dat terwijl (nu in ieder geval) de lengte van deze vector bekend is at compile time en de inhoud ook. Je kunt overwegen hier een array van te maken en zelfs om het const te maken.

Ik heb niet naar de inhoud van je Macro klasse gekeken, maar het lijkt me dat de Active boolean bij het uitvoeren van de Marco hoort, niet bij de Stroke; de Stroke weet immers niets van de tijd sinds het begin van de Macro. Overweeg om Active uit de Stroke te halen en in plaats daarvan dit gedrag te beheren in de Macro zelf (bijvoorbeeld door bij te houden welke Stroke indexes er nog lopen en hoe lang nog).

Nog een overweging: is het reëel dat een Macro langer dan 65000 ticks duurt? Zo niet, dan kan je overwegen om i.p.v. een long een short te gebruiken.

Zoek ook eens iets op over move semantics, het is niet erg intuïtief, maar ik denk dat momenteel iedere Stroke twee of drie keer in het geheugen staat, terwijl dat geheel niet nodig is.

Edit: Ik heb even naar je playMacro functie gekeken.
Ten eerste: stop dit eens als methode in je Macro klasse.
Ten tweede: Je maakt Active nooit false.
Ten derde: Ik zou eerder kijken of een Stroke een tijdstip tussen de laatste en de huidige millis() heeft i.p.v. met een Active vlag te werken.

[ Voor 21% gewijzigd door Verwijderd op 14-08-2017 13:42 ]


Acties:
  • 0 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 27-09 13:03
ThomasG schreef op maandag 14 augustus 2017 @ 09:12:
[...]
Dat mag je niet vanuit gaan..
Correct, vandaar "zouden moeten", ik ga er van uit dat het daarna ook gecontroleerd wordt. Iha is const static voldoende, maar het kan zijn dat je er pragma's, attributes of andere linker instructies voor nodig hebt.
omdat een const helemaal niet geoptimaliseerd kan/mag worden door de compiler. .... blah ...
Dit gedeelte van je reactie raakt kant nog wal.

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


Acties:
  • 0 Henk 'm!

Verwijderd

farlane schreef op maandag 14 augustus 2017 @ 16:05:
[...]

Correct, vandaar "zouden moeten", ik ga er van uit dat het daarna ook gecontroleerd wordt. Iha is const static voldoende, maar het kan zijn dat je er pragma's, attributes of andere linker instructies voor nodig hebt.


[...]

Dit gedeelte van je reactie raakt kant nog wal.
Als je zeker wilt weten dat het in Flash/ROM komt, gebruik constexpr, je hebt dan iets minder mogelijkheid voor wat je er aan toekent, want het moet compile-time bekend zijn. const wordt op zo ongeveer alle platformen in flsah/rom gezet, behalve... de ATmega en de 8051 :X

Acties:
  • 0 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 27-09 13:03
Zal wel iets met Harvard te maken hebben. AVR/8051 sucks :P

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


Acties:
  • 0 Henk 'm!

  • Radiant
  • Registratie: Juli 2003
  • Niet online

Radiant

Certified MS Bob Administrator

Dan moet je er (in gcc) PROGMEM achter zetten en gaan klooien met pgm_read_*.

gcc is nogal getrouwd met Von Neumann waar alles in één adresruimte zit. Andere compilers zoals IAR kunnen wel gewoon goed omgaan met variabelen in flash en EEPROM op de AVR.

Acties:
  • 0 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 27-09 13:03
Radiant schreef op maandag 14 augustus 2017 @ 16:25:
Dan moet je er (in gcc) PROGMEM achter zetten en gaan klooien met pgm_read_*.

gcc is nogal getrouwd met Von Neumann waar alles in één adresruimte zit. Andere compilers zoals IAR kunnen wel gewoon goed omgaan met variabelen in flash en EEPROM op de AVR.
Hoezo? Bij IAR moet je ook mieren om data in ROM te krijgen.

Daarbij, een gescheiden adresruimte is gewoon kut om mee te werken, ik zie niet in wat daar het voordeel van is. Zal een van de redenen zijn dat Cortex-M de defacto standaard is tegenwoordig.

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


Acties:
  • 0 Henk 'm!

  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 13-09 00:05
Gescheiden adresruimte is handig op 16 bittertjes, want twee keer 64kB is beter dan een keer. Voor een 32 bitter is het inderdaad zinloos.

Overigens is Harvard een scheiding code/data, niet RAM/ROM.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


Acties:
  • 0 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 27-09 13:03
MSalters schreef op dinsdag 15 augustus 2017 @ 11:19:
Gescheiden adresruimte is handig op 16 bittertjes, want twee keer 64kB is beter dan een keer. Voor een 32 bitter is het inderdaad zinloos.
Ah ja, in dat licht zal dat zinnig geweest zijn. Wmb is het 8bit tijdperk echter ten einde en hebben we het 16bit tijdperk gewoon (terecht) overgeslagen, dus die architectuur is ook iets voor de geschiedenisboeken.
Overigens is Harvard een scheiding code/data, niet RAM/ROM.
Klopt natuurlijk, maar vaak is het op uC's zo dat als je van de default code=ROM & data=RAM wilt afwijken dat je daar 'iets' voor zult moeten doen.

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


Acties:
  • 0 Henk 'm!

  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 13-09 00:05
Het "8 bits tijdperk" waar jij aan denkt had 16 bits address spaces. Dat is absoluut is niet overgeslagen, integendeel.

Er is simpelweg nooit een tijd geweest dat je CPU 8 bits address spaces had. De Intel 4004 begon met een 12 bits address space, en vanaf daar is het alleen maar gegroeid.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


Acties:
  • 0 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 27-09 13:03
MSalters schreef op dinsdag 15 augustus 2017 @ 15:49:
Het "8 bits tijdperk" waar jij aan denkt had 16 bits address spaces. Dat is absoluut is niet overgeslagen, integendeel.

Er is simpelweg nooit een tijd geweest dat je CPU 8 bits address spaces had. De Intel 4004 begon met een 12 bits address space, en vanaf daar is het alleen maar gegroeid.
Het 16bits tijdperk (met 16bits address space?) hebben we overgeslagen. (min of meer, het aandeel is altijd erg klein geweest tov de 8 bits MCU's (met 16 bit address space)

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


Acties:
  • +1 Henk 'm!

  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 13-09 00:05
Nou ja, de klassieke 16 bitter is de 8086, met een 20 bits address space. En die zou je toch wel invloedrijk mogen noemen. En in DSP's zie je ze nog steeds.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein

Pagina: 1