Elektroda.pl
Elektroda.pl
X
Proszę, dodaj wyjątek www.elektroda.pl do Adblock.
Dzięki temu, że oglądasz reklamy, wspierasz portal i użytkowników.

[Atmel Studio, C] Funkcje w osobnych plikach zajmują więcej pamięci uC

Adam Ś. 23 Kwi 2016 15:37 975 8
  • #1 23 Kwi 2016 15:37
    Adam Ś.
    Poziom 12  

    Wykonałem dzisiaj mały test - porównałem ile pamięci mikrokontrolera będzie zajmował ten sam fragment programu, ale napisany na 3 różne sposoby. Program był pisany w Atmel Studio 7 w języku C na ATmega8.

    1. Na pierwszy ogień stworzyłem przykładowy kod i go skompilowałem. Tutaj nie korzystałem z żadnych funkcji ani też plików nagłówkowych. Kod wyglądał tak:

    Kod: c
    Zaloguj się, aby zobaczyć kod


    Po kompilacji sprawdziłem generowane informacje w tej "konsoli" środowiskowej i oto jak wygląda zużycie pamięci uC:

    Code:
    Program Memory Usage    :   86 bytes   1,0 % Full
    
    Data Memory Usage       :   0 bytes   0,0 % Full


    2. Następnie ten sam program zmodyfikowałem tak, że zastosowałem dwie funkcje. Całość wyglądała tak:

    Kod: c
    Zaloguj się, aby zobaczyć kod


    Tutaj podczas kompilacji otrzymałem:

    Code:
    Program Memory Usage    :   84 bytes   1,0 % Full
    
    Data Memory Usage       :   0 bytes   0,0 % Full


    3. W trzecim sposobie bazowałem na tym z 2, lecz funkcje przeniosłem do osobnego pliku .c i dodatkowo stworzyłem plik nagłówkowy .h

    Oto zawartość pliku testowy.c

    Kod: c
    Zaloguj się, aby zobaczyć kod


    a także plik nagłówkowy tekstowy.h

    Kod: c
    Zaloguj się, aby zobaczyć kod


    I jeszcze zawartość programu głównego

    Kod: c
    Zaloguj się, aby zobaczyć kod


    Tutaj otrzymałem najwięcej bo:
    Code:
    Program Memory Usage    :   100 bytes   1,2 % Full
    
    Data Memory Usage       :   0 bytes   0,0 % Full



    Mam w związku z tym pytanie. Do tej pory zawsze myślałem, że między sposobem 2 a 3 nie ma żadnej różnicy i to czy funkcje są zaszyte w programie czy w osobnym pliku .c oraz .h, nie robi żadnej różycy. Tymczasem widać, że różnica jest i to spora. W związku z tym przychodzi mi do głowy myśl, że np. zamiast używać jakiejś "biblioteki" HD44780.h i ją includować, to lepiej ją wybebeszyć i wszystkie funkcje przenieść do kodu głównego, bo prawdopodobnie zaoszczędzi się sporo miejsca.

    Pytanie więc brzmi czy ta różnica jest normalna czy ja coś źle robię?

    0 8
  • #2 23 Kwi 2016 16:28
    grko
    Poziom 32  

    To jest normalna różnica związana z optymalizacją kodu. Kompilator prawdopodobnie w 2 przypadku zamienił Twoje funkcje na inline. Przenoszenie funkcji do osobnego modułu ma sens jeżeli chcesz z takiej funkcji skorzystać wielokrotnie. Wtedy rzeczywiście oszczędza się miejsce w przeciwieństwie do Twojego przypadku.

    PS 1: Funkcje wykorzystywane tylko w jednym module powinny mieć atrybut static
    PS 2: Jeżeli już chcesz przenieść swoje funkcje do osobnego modułu jednocześnie oszczędzając tych kilka bajtów na wywołanie to daj te funkcje do nagłówka z atrybutem static inline.

    0
  • #3 23 Kwi 2016 16:50
    jnk0le
    Poziom 17  

    Zamiast kombinować można użyć flag `-fwhole-program` albo `-flto` (nie mam pewności czy nadal jest zabugowana)

    Domyślnie w gcc każdy plik jest osobną jednostką kompilacji z której nie ma wglądu do wygenerowanego kodu reszty programu, więc kompilator nie może:

    - osadzić kodu w miejscu wywołania (nie posiada informacji czy funkcje nie są wywoływane z jeszcze innego pliku)
    - zapewnić że przekazywane argumenty nie zostaną zmodyfikowane w wywoływanej funkcji.
    - pominąć konwekcji wywoływania funkcji i zachowywania rejestrów
    http://www.atmel.com/webdoc/AVRLibcReferenceManual/FAQ_1faq_reg_usage.html

    0
  • #4 24 Kwi 2016 14:08
    Adam Ś.
    Poziom 12  

    GrzegorzKostka napisał:
    To jest normalna różnica związana z optymalizacją kodu. Kompilator prawdopodobnie w 2 przypadku zamienił Twoje funkcje na inline.


    GrzegorzKostka napisał:
    PS 2: Jeżeli już chcesz przenieść swoje funkcje do osobnego modułu jednocześnie oszczędzając tych kilka bajtów na wywołanie to daj te funkcje do nagłówka z atrybutem static inline.


    Poczytałem trochę o tym inline i z tego co udało mi się ustalić to jego używanie nie powoduje oszczędności. Wychodzi na to że kod z funkcja typu inline jest po prostu wstawiany w miejsce jego wywołania. Czyli jeśli w programie użyję 5 razy tą samą funkcję typu inline to kompilator po prosu mi wstawi kod tej funkcji do programu aż 5 razy. Zwykła funkcja działa by inaczej bo kod funkcji byłby zawsze jeden a tylko 5 razy następowałby skok do tego kodu. Więc tak naprawdę funkcja typu inline będzie zajmowała więcej miejsca gdy będzie używana kilkakrotnie.

    Zrobiłem kolejny test. Tym razem stworzyłem program obsługujący wyświetlacz LCD 2x16. W tym celu skorzystałem z "biblioteki" znalezionej w internecie i dodałem do projektu pliki HD44780.c oraz HD44780.h. W głównym programie wykonuję tylko dwie funkcje, a mianowicie LCD_Initialize() oraz LCD_WriteText("Witaj"). Po kompilacji tego zajmowanych jest 326 bajtów pamięci.
    Następnie wszystkie funkcje z pliku HD44780.c skopiowałem do programu głównego, a z pliku nagłówkowego .h skopiowałem różne definicje. Potem usunąłem pliki z projektu i skompilowałem program - a więc obsługa wyświetlacza znajdowała się w całości programie głównym a nie w zewnętrznym pliku. Po kompilacji okazało się, że nadal jest zużywane 326 bajtów pamięci.
    Wychodzi na to, że jednak nie ma różnicy pomiędzy trzymaniem wszystkiego w kodzie głównym a rozbijaniem kodu na dodatkowe moduły. Pytanie tylko dlaczego podczas testów z pierwszego postu ta różnica występowała i była dość znacząca?

    0
  • #5 24 Kwi 2016 14:37
    Radek
    Poziom 13  

    Adam Ś. napisał:

    Poczytałem trochę o tym inline i z tego co udało mi się ustalić to jego używanie nie powoduje oszczędności. Wychodzi na to że kod z funkcja typu inline jest po prostu wstawiany w miejsce jego wywołania. Czyli jeśli w programie użyję 5 razy tą samą funkcję typu inline to kompilator po prosu mi wstawi kod tej funkcji do programu aż 5 razy. Zwykła funkcja działa by inaczej bo kod funkcji byłby zawsze jeden a tylko 5 razy następowałby skok do tego kodu. Więc tak naprawdę funkcja typu inline będzie zajmowała więcej miejsca gdy będzie używana kilkakrotnie.


    To zależy oszczędność wystąpi np. gdy wywoływana funkcja będzie przyjmowała wiele parametrów a będzie "krótka". Może się okazać że wywołanie funkcji (odkładanie/kopiowanie parametrów) zajmuje więcej kodu niż "zasadnicza" część funkcji, w takim przypadku inline zajmie mniej kodu.

    Sensownym rozwiązaniem jest jak jnk0le napisał używanie whole program optimization.

    0
  • #6 24 Kwi 2016 14:53
    tadzik85
    Poziom 38  

    po 1. To gdzie funkcja się znajduje nie ma żadnego znaczenia.

    Jeśli jest static optymalizator może ją potraktować bardziej ostro.
    Inline jest tylko wskazówką. o tym czy zostanie ona rozwinięta w miejscu wywołania i tak zależy od jej długości zdaje się, że przy -Os może się tak zdarzyć bez dodania inline.

    po2. Inline należy stosować ze static. w innym przypadku i tak tworzona jest kopia dla wywołań zewnętrznych.

    0
  • #7 24 Kwi 2016 18:45
    grko
    Poziom 32  

    Cytat:

    Poczytałem trochę o tym inline i z tego co udało mi się ustalić to jego używanie nie powoduje oszczędności. Wychodzi na to że kod z funkcja typu inline jest po prostu wstawiany w miejsce jego wywołania. Czyli jeśli w programie użyję 5 razy tą samą funkcję typu inline to kompilator po prosu mi wstawi kod tej funkcji do programu aż 5 razy. Zwykła funkcja działa by inaczej bo kod funkcji byłby zawsze jeden a tylko 5 razy następowałby skok do tego kodu. Więc tak naprawdę funkcja typu inline będzie zajmowała więcej miejsca gdy będzie używana kilkakrotnie.


    Stosowanie inline powoduje w niektórych przypadkach oszczędność kodu. Przykładowo jeżeli wywołujesz funkcję 1 raz to zawsze funkcja inline zajmie mniej miejsca niż normalne wywołanie. Dla trywialnych funkcji typu:

    Kod: c
    Zaloguj się, aby zobaczyć kod


    inline jest obowiązkowe ze względu na wydajność oraz zajętość kodu.

    Cytat:

    Wychodzi na to, że jednak nie ma różnicy pomiędzy trzymaniem wszystkiego w kodzie głównym a rozbijaniem kodu na dodatkowe moduły. Pytanie tylko dlaczego podczas testów z pierwszego postu ta różnica występowała i była dość znacząca?


    Różnica była znacząca bo kompilator mógł bez problemu mógł zamienić na inline funkcje z Twojego testu. Z obsługą wyświetlacza nie poszło mu już tak prosto i tego nie zrobił. Dlatego nie zauważyłeś różnicy.

    Ogólnie to warto rozdzielać kod na moduły. Taka biblioteka do wyświetlacza idealnie pasuje do tego schematu. Trywialne procedury, składające się z kilku linii warto wtedy umieścić w nagłówku jako static inline. Bardziej skomplikowane zostawić w pliku *.c.

    0
  • #8 24 Kwi 2016 23:04
    2675900
    Użytkownik usunął konto  
  • #9 24 Kwi 2016 23:11
    tadzik85
    Poziom 38  

    Piotrus_999 napisał:
    Jak zrobic inline aby była widoczna dla wywołać zewnętrznych? Metody nie widze.

    1. Jezeli funkcja jest w pliku nagłówkowym to oczywiście zostaie z-inline-owana.


    BZDURA.

    To, że jest w nagłówku nie oznacza, że będzie inlinowana. Po prostu powstanie wiele instancji tej funkcji. I dlatego jeśli mamy pewność, że funkcja jest dość krótka możemy umieścić ją jako static inline w nagłówku.

    Jeśli będzie za długa (co spowoduje jej wywoływanie) otrzymamy wiele kopii wynikowych tej samej funkcji.

    0