logo elektroda
logo elektroda
X
logo elektroda
REKLAMA
REKLAMA
Adblock/uBlockOrigin/AdGuard mogą powodować znikanie niektórych postów z powodu nowej reguły.

AVR: Umiejscowienie stałych i optymalizacja pamięci w GCC

tmgofer 20 Lip 2007 00:08 3402 14
REKLAMA
  • #1 4097214
    tmgofer
    Poziom 12  
    Posty: 84
    Pomógł: 1
    Ocena: 1
    Witam wszystkich!

    Mam kilka pytań do szanownych kolegów:

    a) Gdzie w pamięci lądują stałe podane w kodzie programu w ten sposób:

    ...
    if (i=50) licznik = 100;
    ...
    if (b<-100)&&(b>100) b=0;
    ...

    (mam na myśli "50","100","-100","0")

    b) Czy jeśli wysyłam wielokrotnie w różnych miejscach programu komendę czyszczenia LCD (czy inną)

    ...
    write_command(0x40);
    ...
    write_command(0x40);
    ...
    ...
    ...
    write_command(0x40);
    ...
    

    to czy kompilator tworzy jakąś stałą i ta wartość jest pamiętana tylko raz?



    c)Czy jeśli mam 10 zmiennych, którym w momencie deklaracji nadaję wartość 0,

    unsigned char a=0,b=0,c=0.... ;


    to muszę się liczyć ze stratą 10 bajtów pamięci?

    d)Na koniec jeszcze proszę o podpowiedź - jak sprawdzić, ile RAMu jest już zajęte, a ile wolne?

    Pozdrawiam!!!
  • REKLAMA
  • #2 4097292
    Konto nie istnieje
    Poziom 1  
  • #3 4098306
    BoskiDialer
    Poziom 34  
    Posty: 1530
    Pomógł: 353
    Ocena: 42
    d) To, ile jest zużytej pamięci RAM w sprzyjających warunkach można policzyć. Ogólnie pamięć wewnętrzna jest rozlokowana jakoś tak:
    data - bss - wolne - stos
    teoretycznie można pobrać adres pierwszej wolnej komórki, wierzchołek stosu i można policzyć ile pamięci jest wolne - aczkolwiek 'wolne' to pojęcie względne - wywołanie funkcji, przerwań etc będzie tą pamięć uszczuplać. Inaczej wygląda sprawa, kiedy ma się do mikrokontrolera podłączoną zewnętrzną pamięć i do tego menager pamięci.
  • REKLAMA
  • #4 4098809
    aster11
    Poziom 19  
    Posty: 211
    Pomógł: 36
    Ocena: 1
    ad c)

    Cytat:
    Czy jeśli mam 10 zmiennych, którym w momencie deklaracji nadaję wartość 0, to muszę się liczyć ze stratą 10 bajtów pamięci?


    Wartości inicjalizacyjne zmiennych są oczywiście, jak to powiedziano, przechowywane w pamięci stałej (np. flash). Można tu jeszcze zwrócić uwagę na następujące szczegóły:

    1. Trudno dać głowę, że liczba bajtów zajętych przez wartości inicjalizacyjne będzie proporcjonalna do liczby inicjalizowanych zmiennych. Np. gdy kolejne wartości inicjalizujące są identyczne (jak w przykładzie), kompilator może zastosować pewne optymalizacje.

    2. Pamięciowy koszt inicjalizacji to nie tylko same wartości inicjalizujące, zajmujące pamięć, ale także (a może bardziej) same instrukcje inicjalizacji, które oczywiście także zajmują pamięć programu.

    3. Inicjalizacja wartościami zerowymi (jak w przykładzie) ma charakter szczególny. W przypadku zmiennych globalnych i statycznych zmiennych lokalnych jest to początkowa wartość domyślna, gwarantowana przez standard języka C (technicznie realizacja tego polega na umieszczeniu takich zmiennych w początkowo "wyczyszczonym" obszarze). W przypadku takich zmiennych ich jawna inicjalizacja (wartościami zerowymi) ma charakter nadmiarowy i rzeczywiście zajmuje niepotrzebnie miejsce w pamięci programu - chyba, że odpowiednia zdolność optymalizacji kompilatora potrafi to wykryć i wyeliminować.
  • #5 4098942
    tmgofer
    Poziom 12  
    Posty: 84
    Pomógł: 1
    Ocena: 1
    Cytat:
    write_command(0x40) - to nie jest funkcja GCC, jest to jakaś funkcja, którą stworzył autor tego programu, który cytujesz;


    Nie no, to oczywista sprawa :P sam ją pisałem. Chodziło raczej o 0x40


    Cytat:
    gdy kolejne wartości inicjalizujące są identyczne (jak w przykładzie), kompilator może zastosować pewne optymalizacje.


    Dokładnie to jest sedno całej sprawy. Czyli jednak kompilator może wykazać się taką spostrzegawczością i zapamiętać wartość w jakiejś jednej komórce pamięci... Nie wie ktoś przypadkiem, czy któraś z optymalizacji (1,2,3,s) gwarantuje takie postępowanie kompilatora?

    Cytat:
    Inicjalizacja wartościami zerowymi (jak w przykładzie) ma charakter szczególny. W przypadku zmiennych globalnych i statycznych zmiennych lokalnych jest to początkowa wartość domyślna, gwarantowana przez standard języka C


    Warto wiedzieć... :)
  • #6 4099016
    marek_Łódź
    Poziom 36  
    Posty: 3103
    Pomógł: 208
    Ocena: 66
    Możesz sobie zdezasemblować kod i obejrzeć reprezentację. W przypadku write...(0x40) stworzenie wspólnej "czterdziestki" dla wszystkich wywołań jest mało opłacalne bo prosciej jest pobrać tę wartość bezpośrednio w obszarze wywołania, jak tworzyć dodatkowe odsyłacze do wspólnej komórki.
  • REKLAMA
  • #7 4099154
    szelus
    Poziom 34  
    Posty: 1508
    Pomógł: 315
    Ocena: 53
    Może trochę spróbuję podsumować, bo, sądząc z pytań, odpowiedzi kolegow powyżej, aczkolwiek poprawne, moga być na nieco zbyt wysokim poziomie abstrakcji :)

    1. Stałe są umieszczane w pamięci programu, a nie pamięci danych.
    2. AVR gcc preferuje umieszczanie stałych wewnątrz kodu rozkazu umieszczającego ją w rejestrze. Krócej się nie da, bo załadowanie danej z pamięci flash (umieszczonej poza rozkazem) do rejestru zajmie więcej kodu.
    3. Zmienne globalne (i statyczne) z wartością początkową inna niż 0 są umieszczane w ciągłym obszarze RAM, tak że ich wartości zapisane w ciągłym obszarze pamięci FLASH zostaną przepisane na poczatku programu prostą petlą (rodzaj funkcji) memcpy. Też się tego nie da raczej zrobić efektywnie.
    3. Nie wiem, jak dla "dużych" zmiennych automatycznych w rodzaju:
    
    struct abc {
      int a;
      int b;
      int c;
    };
    
    int foo(void)
    {
        struct abc x = {1,2,3};
    
    } 
    
    

    ale raczej unikam takich konstrukcji dla mikrokontrolerów :wink:


    4. W przykładowym makefile-u w WinAVR jest generowanie listingu (.lst).
    Wykonując make lst dostajemy taki listing, w którym dokładnie rozpisane jest wykorzystanie pamięci. Ręcznie można to uzyskać wykonując komedę

    avr-objdump -h prog.elf

    Aby ustalić zajętość RAM należy dodać do siebie rozmiary sekcji .data .bss .noinit i (zakładany) rozmiar stosu.
  • REKLAMA
  • #8 4100000
    marek_Łódź
    Poziom 36  
    Posty: 3103
    Pomógł: 208
    Ocena: 66
    W przypadku stałych, których rozmiar przekracza możliwości alokowania w pamięci RAM, można wykorzystać mechanizmy umożliwiające ich alokowanie w pamięci kodu np funkcje dostępu do pamięci kodu pgmspace.h

    przykład wycięty z jakiegoś programiku:

    .....
    #include <avr/pgmspace.h>
    
    #define Word uint16_t
    #define Byte uint8_t
    
    .....
    //Stałe w pamięci kodu
    
    Byte XCode[0x1000] PROGMEM = 
      {0x00, 0x13, 0x02, 0x00, 0x08, 0x00, 0x00, 0x00, 0x16, 0x00, 0x2E };
    
    
    #define  GetCodeByte(n) pgm_read_byte_near(&XCode[n])
    #define  GetCodeWord(n) pgm_read_word_near(&XCode[n])


    Tak w prosty sposób możemy zdefiniować np. stałe teksty, sekwencje sterujące wyświetlaczem itp.
  • #9 12978569
    _pieczas
    Poziom 12  
    Posty: 113
    Ocena: 3
    Panowie.
    W moim programie dla atmegi32 mam 2 tablice typu uint32_t. Te tablice muszą (?) mieć charakter statyczny i pracują w innej fukcji niż main. Ściśle mówiąc będą wywoływane przez timer. Problem polega na tym, że gdy tylko dostaną przydomek "static" ich objętość rośnie. I tak 2 zmienne uint32 po 256 elementów zapełniają mi pamięć w 104%. Gdy przerzucę je poza obszar funkcji i zadeklaruje jako globalne to historia taka sama. Czy można ominąć ten problem zachowując ich statyczność? Przy każdym wykonaniu funkcji dopisywany jest jeden element przy czym nie mogą zostać zapomniane starsze elementy, a gdy tablica się przepełni wówczas nadpisywana jest od początku (FIFO).
  • #10 12978651
    zumek
    Poziom 39  
    Posty: 3352
    Pomógł: 695
    Ocena: 52
    _pieczas napisał:
    ... I tak 2 zmienne uint32 po 256 elementów zapełniają mi ...
    ...całą dostępna w ATMega32 pamięć RAM. Wytłumacz proszę, o co Ci DOKŁADNIE chodzi.
  • #11 12978945
    _pieczas
    Poziom 12  
    Posty: 113
    Ocena: 3
    Kompilacja nie następuje gdyż "Data memory usage = 104%Full". Po wykomentowaniu "static" zajętość pamięci wynosi kilka procent.
    Chodzi mi o to, że tablice te są konieczne w moim programie. Muszą one być statyczne - nieulotne po wyjściu z funkcji. Jednakże ustawiając je jako statyczne powoduję że zapełniają pamięć. Gdy jednak nie są statyczne wówczas program nie działa poprawnie.
  • #12 12978994
    tadzik85
    Poziom 38  
    Posty: 3404
    Pomógł: 415
    Ocena: 16
    Przecież to oczywiste skoro wykorzystujesz więcej RAMu niż posiadasz.
    Co tym zmiennej nie ma tu znaczenia.
    lokalna static czy nie gdzieś musi się znajdować.
  • #13 12979009
    excray
    Poziom 41  
    Posty: 5500
    Pomógł: 739
    Ocena: 656
    Oznacza to że wybrałeś nie właściwy mikroprocesor do swojego projektu. Za mało RAM.
  • #14 12979230
    tmgofer
    Poziom 12  
    Posty: 84
    Pomógł: 1
    Ocena: 1
    _pieczas napisał:
    Ściśle mówiąc będą wywoływane przez timer

    Ściśle mówiąc to funkcja obsługi przerwania (od przepełnienia/porównania) timera będzie na nich wykonywała jakieś operacje (np. zapisu)

    _pieczas napisał:
    Problem polega na tym, że gdy tylko dostaną przydomek "static" ich objętość rośnie

    Jak umieszczasz je wewnątrz funkcji bez "static" to mają dokładnie taką samą wielkość, ale lądują na stosie jako zmienne automatyczne. Dlatego kompilator nie wykrywa błędu w czasie kompilacji. Nie jest świadomy, że w tym przypadku stos się przepełni.

    Tak jak wspomnieli koledzy wcześniej, procek ma za mało pamięci RAM na zadanie, które przed nim postawiłeś.

Podsumowanie tematu

✨ Stałe wartości liczbowe w kodzie AVR GCC są umieszczane w pamięci programu (flash) jako dane natychmiastowe, co pozwala na efektywne wykorzystanie pamięci. Kompilator zwykle nie tworzy osobnych stałych dla powtarzających się wartości, takich jak 0x40 w wywołaniach funkcji, ponieważ prostsze jest bezpośrednie wstawienie wartości w kodzie rozkazu. Zmienne globalne i statyczne z inicjalizacją inną niż zero są przechowywane w RAM, a ich wartości początkowe kopiowane są z pamięci flash podczas startu programu. Inicjalizacja zerowa zmiennych globalnych i statycznych jest realizowana przez umieszczenie ich w obszarze bss, który jest zerowany przy starcie, co nie powoduje dodatkowego zajęcia pamięci flash. Zużycie pamięci RAM można oszacować, analizując układ pamięci (data, bss, wolne, stos) i porównując adresy wolnej pamięci i stosu, jednak jest to wartość zmienna w czasie działania programu. W przypadku dużych tablic statycznych w mikrokontrolerze ATmega32, przekroczenie dostępnej pamięci RAM (np. 104% użycia) powoduje błąd kompilacji. Tablice statyczne zajmują pamięć RAM niezależnie od miejsca deklaracji (lokalne static czy globalne). Problem ten można rozwiązać przez wybór mikrokontrolera z większą pamięcią RAM lub optymalizację kodu. Dla dużych stałych można stosować mechanizmy umieszczania danych w pamięci programu za pomocą biblioteki pgmspace.h, co pozwala na oszczędność RAM.
REKLAMA