Używam za dużo pamięci RAM. Jak można to zmierzyć?

19

Chciałbym wiedzieć, ile pamięci RAM używam w swoim projekcie, o ile mogę stwierdzić, że nie ma sposobu, aby to naprawdę rozwiązać (oprócz samodzielnego przejrzenia i obliczenia). Doszedłem do etapu w dość dużym projekcie, w którym ustaliłem, że brakuje mi pamięci RAM.

Ustaliłem to, ponieważ mogę dodać sekcję, a potem piekło rozpada się gdzie indziej w moim kodzie bez wyraźnego powodu. Jeśli mam #ifndefcoś innego, to znowu działa. Z nowym kodem nie ma nic programowo niepoprawnego.

Przez chwilę podejrzewałem, że zbliżam się do końca dostępnej pamięci RAM. Nie sądzę, że używam zbyt dużo stosów (chociaż jest to możliwe), jaki jest najlepszy sposób na określenie, ile pamięci RAM faktycznie używam?

Przechodząc i próbując to wypracować, mam problemy, gdy dochodzę do wyliczeń i struktur; ile kosztują pamięci?

pierwsza edycja: RÓWNIEŻ edytowałem szkic od samego początku, to nie są rzeczywiste wyniki, które początkowo otrzymałem, ale to, co teraz otrzymuję.

  text     data     bss     dec     hex filename
 17554      844     449   18847    499f HA15_20140317w.cpp.elf
 16316      694     409   17419    440b HA15_20140317w.cpp.elf
 17346      790     426   18562    4882 HA15_20140317w.cpp.elf

Pierwszy wiersz (z tekstem 17554) nie działał, po wielu edycjach drugi wiersz (z tekstem 16316) działa tak, jak powinien.

edycja: trzeci wiersz ma wszystko, co działa, odczyt szeregowy, moje nowe funkcje itp. Zasadniczo usunąłem niektóre zmienne globalne i zduplikowałem kod. Wspominam o tym, ponieważ (jak podejrzewam) nie chodzi o ten kod per sae, musi dotyczyć użycia pamięci RAM. To prowadzi mnie z powrotem do pierwotnego pytania: „jak najlepiej to zmierzyć”. Wciąż sprawdzam kilka odpowiedzi, dzięki.

Jak właściwie interpretować powyższe informacje?

Jak dotąd rozumiem:

`TEXT` is program instruction memory
`DATA` is variables (unitialised?) in program memory
`BSS`  is variables occupying RAM

skoro BSS ma znacznie mniej niż 1024 bajty, dlaczego drugi działa, a pierwszy nie? Jeśli tak, DATA+BSSoba zajmują więcej niż 1024.

reedycja: Zredagowałem pytanie, aby dołączyć kod, ale teraz go usunąłem, ponieważ tak naprawdę nie miało to nic wspólnego z problemem (poza może złymi praktykami kodowania, deklaracjami zmiennych i tym podobnymi). Możesz przejrzeć kod, przeglądając wprowadzone zmiany, jeśli naprawdę chcesz go zobaczyć. Chciałem wrócić do pytania, które było bardziej oparte na: Jak zmierzyć zużycie pamięci RAM.

Madivad
źródło
Pomyślałem, że dodam, dodałem różne nowe sekcje kodu w ciągu ostatnich kilku tygodni, a następnie zoptymalizowałem go, aż zadziała, ale teraz dodałem tylko pół tuzina bajtów i skończyłem ... :(
Madivad
Czy używasz Stringpisania w swoich programach? Jest to znane z częstego przydzielania i zwalniania pamięci dynamicznej, co może rozdrobnić stertę do punktu, w którym może nie pozostać żadna pamięć.
jfpoilpret
@jfpoilpret Trzymam się z dala od Strings z powodu narzutu. Z przyjemnością pracuję z tablicami znaków, co powiedziawszy, prawie zawsze definiuję wszystkie moje tablice znaków z ustalonym rozmiarem (w tej chwili mam JEDNĄ tablicę bajtów, która nie jest czysta, ponieważ zmieniam długość zawartości dla różnych rekompilacji.
Madivad
Opublikowanie tutaj kodu (lub wklejenia go, jeśli jest zbyt duży) może pomóc dowiedzieć się, jakie problemy napotykasz w pamięci.
jfpoilpret
@ jfpoilpret Naprawdę nie mogę opublikować kodu, jest ogromny i niestety bardzo rozdęty, rozłożony na 16 plików. Był to projekt, który pozwalałem znacznie wykroczyć poza to, co było wymagane (kilka projektów połączonych razem). Zaczynam teraz to rozbijać, co na pewno pomoże rozwiązać problem. Chociaż są pewne części, na które muszę spojrzeć (lub poprowadzić mnie), opublikuję je później.
Madivad

Odpowiedzi:

15

Możesz użyć dostarczonych funkcji AVRGCC: Monitorowanie użycia stosu

Ta funkcja miała na celu sprawdzenie użycia stosu, ale zgłasza rzeczywistą pamięć RAM, która nigdy nie była używana (podczas wykonywania). Robi to poprzez „malowanie” (wypełnianie) pamięci RAM znaną wartością (0xC5), a następnie sprawdzanie obszaru pamięci RAM, licząc, ile bajtów ma wciąż tę samą wartość początkową.
Raport pokaże pamięć RAM, która nie została wykorzystana (minimalna ilość wolnej pamięci RAM) i dlatego możesz obliczyć maksymalną pamięć RAM, która została wykorzystana (Całkowita pamięć RAM - zgłoszona pamięć RAM).

Istnieją dwie funkcje:

  • StackPaint jest wykonywany automatycznie podczas inicjalizacji i „maluje” pamięć RAM wartością 0xC5 (w razie potrzeby można ją zmienić).

  • StackCount można wywołać w dowolnym momencie, aby policzyć pamięć RAM, która nie była używana.

Oto przykład użycia. Nie robi wiele, ale ma na celu pokazać, jak korzystać z funkcji.

// -----------------------------------------------------------------------------
extern uint8_t _end;
extern uint8_t __stack;

void StackPaint(void) __attribute__ ((naked)) __attribute__ ((section (".init1")));

void StackPaint(void)
{
#if 0
    uint8_t *p = &_end;

    while(p <= &__stack)
    {
        *p = 0xc5;
        p++;
    }
#else
    __asm volatile ("    ldi r30,lo8(_end)\n"
                    "    ldi r31,hi8(_end)\n"
                    "    ldi r24,lo8(0xc5)\n" /* STACK_CANARY = 0xc5 */
                    "    ldi r25,hi8(__stack)\n"
                    "    rjmp .cmp\n"
                    ".loop:\n"
                    "    st Z+,r24\n"
                    ".cmp:\n"
                    "    cpi r30,lo8(__stack)\n"
                    "    cpc r31,r25\n"
                    "    brlo .loop\n"
                    "    breq .loop"::);
#endif
} 


uint16_t StackCount(void)
{
    const uint8_t *p = &_end;
    uint16_t       c = 0;

    while(*p == 0xc5 && p <= &__stack)
    {
        p++;
        c++;
    }

    return c;
} 

// -----------------------------------------------------------------------------

void setup() {

Serial.begin(9600);
}

void loop() {
  // put your main code here, to run repeatedly:
Serial.println(StackCount(), DEC);  // calls StackCount() to report the unused RAM
delay(1000);
}
alexan_e
źródło
ciekawy kawałek kodu, dzięki. Użyłem go i sugeruje, że dostępnych jest ponad 600 bajtów, ale gdy zakopuję to w głębszych subwooferach, zmniejsza się, ale nie usuwa. Więc MOŻE mój problem jest gdzie indziej.
Madivad
@Madivad Pamiętaj, że ponad 600 bajtów reprezentuje minimalną dostępną ilość pamięci RAM aż do momentu wywołania StackCount. Tak naprawdę nie ma znaczenia, jak głęboko umieścisz wywołanie, jeśli większość kodu i zagnieżdżone wywołania zostały wykonane przed wywołaniem StackCount, wynik będzie poprawny. Na przykład możesz zostawić swoją tablicę na jakiś czas (tak długo, jak potrzeba, aby uzyskać wystarczającą ilość kodu lub idealnie, dopóki nie dostaniesz opisanego niewłaściwego zachowania), a następnie naciśnij przycisk, aby uzyskać zgłoszoną pamięć RAM. Jeśli jest wystarczająco, to nie jest to przyczyną problemu.
alexan_e
1
Dzięki @alexan_e, utworzyłem obszar na moim ekranie, który teraz to zgłasza, więc w miarę postępów w ciągu następnych kilku dni będę obserwował ten numer z zainteresowaniem, szczególnie gdy się nie powiedzie!
Jeszcze
@Madivad Należy pamiętać, że dana funkcja nie zgłasza poprawnych wyników, jeśli w kodzie użyto
alexan_e
dziękuję za to, jestem świadom, że zostało wspomniane. O ile mi wiadomo, nie korzystam z niego (wiem, że może to być biblioteka, nie sprawdziłem jeszcze w pełni).
Madivad
10

Główne problemy związane z użyciem pamięci w środowisku wykonawczym to:

  • brak dostępnej pamięci w stercie dla alokacji dynamicznych ( malloclub new)
  • podczas wywoływania funkcji nie ma już miejsca na stosie

Oba są w rzeczywistości takie same, jak AVR SRAM (2K na Arduino) jest używany dla obu (oprócz danych statycznych, których rozmiar nigdy się nie zmienia podczas wykonywania programu).

Zasadniczo dynamiczna alokacja pamięci jest rzadko używana na MCU, zazwyczaj korzysta z niej tylko kilka bibliotek (jedna z nich to Stringklasa, o której wspomniałeś, że nie używasz, i to jest dobry punkt).

Stos i stos można zobaczyć na poniższym obrazku (dzięki uprzejmości Adafruit ): wprowadź opis zdjęcia tutaj

Dlatego najbardziej oczekiwanym problemem jest przepełnienie stosu (tj. Gdy stos rośnie w kierunku stosu i przepełnia się nim, a następnie - jeśli stos nie był w ogóle używany - przepełnienia w strefie danych statycznych SRAM. W tym czasie masz wysokie ryzyko:

  • uszkodzenie danych (tj. stos zastępuje dane sterty lub dane statyczne), co daje niezrozumiałe zachowanie
  • uszkodzenie stosu (tj. stos lub dane statyczne zastępują zawartość stosu), co zwykle prowadzi do awarii

Aby poznać ilość pamięci pozostałej między górą stosu a górą stosu (w rzeczywistości możemy nazwać to dnem, jeśli reprezentujemy zarówno stos, jak i stos na tym samym obrazie, jak pokazano poniżej), należy może korzystać z następującej funkcji:

int freeRam () {
  extern int __heap_start, *__brkval; 
  int v; 
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}

W powyższym kodzie __brkvalwskazuje na górę stosu, ale 0wtedy, gdy stos nie został użyty, w którym to przypadku używamy, &__heap_startktóre punkty wskazują __heap_startpierwszą zmienną, która oznacza spód stosu; &vwskazuje oczywiście na górę stosu (jest to ostatnia zmienna wypychana na stos), stąd powyższa formuła zwraca ilość pamięci dostępnej dla stosu (lub stosu, jeśli go używasz) do powiększenia.

Możesz użyć tej funkcji w różnych lokalizacjach kodu, aby dowiedzieć się, gdzie ten rozmiar dramatycznie się zmniejsza.

Oczywiście, jeśli kiedykolwiek zobaczysz, że ta funkcja zwraca liczbę ujemną, jest już za późno: już przepełniłeś stos!

jfpoilpret
źródło
1
Do moderatorów: przepraszam, że umieściłem ten post na wiki społeczności, musiałem coś popsuć podczas pisania, w środku postu. Odłóż to tutaj, ponieważ ta akcja była niezamierzona. Dzięki.
jfpoilpret
dziękuję za tę odpowiedź, dosłownie TYLKO znalazłem ten fragment kodu zaledwie godzinę temu (na dole placu zabaw.arduino.cc/Code/AvailableMemory#.UycOrueSxfg ). Nie dodałem go jeszcze (ale zrobię to), ponieważ mam dość duży obszar do debugowania na ekranie. Myślę, że byłem zdezorientowany co do dynamicznego przypisywania rzeczy. Czy malloci newjedyny sposób mogę to zrobić? Jeśli tak, to nie mam nic dynamicznego. Właśnie dowiedziałem się, że UNO ma 2K SRAM. Myślałem, że to 1K. Biorąc to pod uwagę, nie brakuje mi pamięci RAM! Muszę szukać gdzie indziej.
Madivad
Ponadto istnieje również calloc. Ale możesz używać bibliotek innych firm, które korzystają z alokacji dynamicznej bez Twojej wiedzy (musisz sprawdzić kod źródłowy wszystkich swoich zależności, aby się tego upewnić)
jfpoilpret
2
Ciekawy. Jedynym „problemem” jest to, że zgłasza wolną pamięć RAM w punkcie, w którym jest wywoływana, więc jeśli nie zostanie umieszczona we właściwej części, możesz nie zauważyć przekroczenia stosu. Wydana przeze mnie funkcja wydaje się mieć przewagę w tym obszarze, ponieważ zgłasza minimalną ilość wolnej pamięci RAM do tego momentu, po użyciu adresu pamięci RAM nie jest już zgłaszana jako wolna (z drugiej strony może być trochę zajętej pamięci RAM bajty, które pasują do wartości „malowania” i są zgłaszane jako wolne). Poza tym może jeden sposób pasuje lepiej niż drugi, w zależności od tego, czego chce użytkownik.
alexan_e
Słuszna uwaga! Nie zauważyłem tego konkretnego punktu w twojej odpowiedzi (i dla mnie, który faktycznie wyglądał jak błąd), teraz widzę sens „malowania” wolnej strefy z góry. Może mógłbyś sprecyzować ten punkt w swojej odpowiedzi?
jfpoilpret
7

Kiedy zastanawiasz się, jak zlokalizować wygenerowany plik .elf w katalogu tymczasowym, możesz wykonać poniższe polecenie, aby zrzucić użycie pamięci SRAM i project.elfzastąpić je wygenerowanym .elfplikiem. Zaletą tego wyjścia jest możliwość sprawdzenia, w jaki sposób używana jest pamięć SRAM. Czy wszystkie zmienne muszą być globalne, czy naprawdę wszystkie są wymagane?

avr-objdump -S -j .bss project.elf

project.elf:     file format elf32-avr


Disassembly of section .bss:

00800060 <__bss_start>:
        ...

00800070 <measurementReady>:
        ...

00800071 <cycles>:
        ...

00800073 <measurement>:
  800073:       00 00 00 00                                         ....

00800077 <measurementStart>:
  800077:       00 00 00 00                                         ....

0080007b <timerOverflows>:
  80007b:       00 00 00 00

Zauważ, że nie pokazuje to wykorzystania pamięci dynamicznej i pamięci, jak zauważył Ignacio Vazquez-Abrams w komentarzach poniżej.

Dodatkowo avr-objdump -S -j .data project.elfmożna sprawdzić, ale żaden z moich programów nic z tym nie wypisuje, więc nie jestem pewien, czy jest to przydatne. To ma listy „zainicjowana (niezerowej) dane”.

jippie
źródło
Lub możesz po prostu użyć avr-size. Ale to nie pokazuje dynamicznych alokacji ani użycia stosu.
Ignacio Vazquez-Abrams,
@ IgnacioVazquez-Abrams o dynamice, to samo dla mojego rozwiązania. Edytowałem moją odpowiedź.
jippie
Ok, to jak dotąd najciekawsza odpowiedź. Eksperymentowałem avr-objdumpi avr-sizewkrótce będę edytować swój post powyżej. Dzięki za to.
Madivad
3

Przez chwilę podejrzewałem, że zbliżam się do końca dostępnej pamięci RAM. Nie sądzę, że używam zbyt dużo stosów (chociaż jest to możliwe), jaki jest najlepszy sposób na określenie, ile pamięci RAM faktycznie używam?

Najlepiej byłoby zastosować kombinację ręcznego oszacowania i sizeofoperatora. Jeśli wszystkie deklaracje są statyczne, powinno to dać dokładny obraz.

Jeśli używasz alokacji dynamicznych, możesz napotkać problem po rozpoczęciu zwalniania pamięci. Wynika to z fragmentacji pamięci na stercie.

Przechodząc i próbując to wypracować, mam problemy, gdy dochodzę do wyliczeń i struktur; ile kosztują pamięci?

Wyliczenie zajmuje tyle samo miejsca co int. Tak więc, jeśli masz zestaw 10 elementów w enumdeklaracji, byłoby to 10*sizeof(int). Ponadto każda zmienna korzystająca z wyliczenia jest po prostu an int.

W przypadku struktur najłatwiej byłoby sizeofto sprawdzić. Struktury zajmują (minimalną) przestrzeń równą sumie jej elementów. Jeśli kompilator wykonuje wyrównanie struktury, może być więcej, jednak jest to mało prawdopodobne w przypadku avr-gcc.

asheeshr
źródło
Wszystko statycznie przypisuję tak dalece, jak to możliwe. Nigdy nie myślałem o użyciu sizeofdo tego celu. W tej chwili mam już prawie 400 bajtów (globalnie). Teraz przejdę i obliczyć wyliczenia (ręcznie) i struktury (których mam kilka - i użyję sizeof), i przekażę raport.
Madivad
Nie jestem pewien, czy naprawdę potrzebujesz sizeofznać rozmiar swoich danych statycznych, ponieważ są one drukowane przez avrdude IIRC.
jfpoilpret
@jfpoilpret To chyba zależy od wersji. Nie wszystkie wersje i platformy to zapewniają. Moje (Linux, wiele wersji) nie pokazują użycia pamięci dla jednego, podczas gdy wersje Maca tak.
asheeshr
Przeszukałem pełne wyniki, myślałem, że powinno tam być, nie ma
Madivad
@AsheeshR Nie wiedziałem o tym, moje działa dobrze w systemie Windows.
jfpoilpret
1

Istnieje program o nazwie Arduino Builder, który zapewnia schludną wizualizację ilości pamięci flash, SRAM i EEPROM, z których korzysta Twój program.

Arduino Builder

Konstruktor Arduino stanowi część rozwiązania IDE CodeBlocks Arduino . Może być używany jako samodzielny program lub za pomocą CodeBlocks Arduino IDE.

Niestety Arduino Builder jest trochę stary, ale powinien działać dla większości programów i większości Arduinos, takich jak Uno.

sa_leinad
źródło