Elektroda.pl
Elektroda.pl
X

Search our partners

Find the latest content on electronic components. Datasheets.com
Elektroda.pl
IGE-XAO
Please add exception to AdBlock for elektroda.pl.
If you watch the ads, you support portal and users.

Uruchamianie zaawansowanych aplikacji C++ na mikrokontrolerach

ghost666 03 Sep 2021 18:00 2559 25
  • Tworzenie aplikacji użytkownika w C++ ma wiele zalet, więc nie jest zaskakujące, że język ten staje się coraz bardziej popularny wszędzie, w tym w systemach opartych na małych mikrokontrolerach. Projekt „mbed” jest w pełni skoncentrowany na tym języku. Wiele RTOSów zapewnia warstwę kompatybilności z C++, ale w przeciwieństwie do „dużych” systemów (tj. takich, które wyposażone są w MMU), większość RTOSów ma pewne ograniczenia. W poniższym artykule przyjrzymy się wnętrzu implementacji C++ i poznamy przyczyny tych ograniczeń.

    Istnieją dwa główne ograniczenia dla C++ uruchamianego na mikrokontrolerze: ponowne uruchamianie aplikacji i wielowątkowe funkcje standardowej biblioteki C++.

    Większość przykładów w artykule zostanie omówiona w systemie Embox RTOS. Ten system czasu rzeczywistego (RTOS) umożliwia uruchamianie tak złożonych projektów C++ jak OpenCV na niewielkich mikrokontrolerach (MCU). Projekt ten wymaga obsługi wątków w standardowej bibliotece C++. Ponadto Embox, w przeciwieństwie do innych RTOSów na MCU, umożliwia ponowne uruchomienie aplikacji C++. W przykładach wykorzystano płytkę deweloperską z STM32F769i z zewnętrzną pamięcią SDRAM, aby zademonstrować OpenCV, ponieważ struktura ta wymaga setek kilobajtów pamięci RAM. Jednak kilka kilobajtów pamięci RAM wystarczy do uruchomienia prostych aplikacji C++.

    Podstawowa składnia

    Składnia języka C++ jest implementowana przez kompilator. Te funkcje są zawarte w bibliotece obsługi języków o nazwie „libsupc++”. Istnieje kilka części, które muszą być obsługiwane podczas działania aplikacji. Na przykład konieczne jest obsłużenie globalnych konstruktorów i destruktorów.

    Globalne konstruktory i destruktory

    Przyjrzyjmy się, jak każda aplikacja C++ współpracuje z globalnymi konstruktorami i destruktorami. Wszystkie globalne obiekty C++ są tworzone przed wywołaniem przez program funkcji main(). W tym celu istnieją specjalne sekcje: .init_array, .init, .preinit_array, .ctors. Są to tablice wskaźników do funkcji, przez które należy przejść od początku do końca wywołując odpowiedni element tablicy.

    Kod do wywoływania konstruktorów obiektów globalnych wygląda następująco:

    Code: c
    Log in, to see the code



    Zobaczmy też, jak działa kończenie aplikacji C++, a mianowicie wywołanie destruktorów obiektów globalnych. Istnieją dwa sposoby. Po pierwsze, najczęściej stosowanym w kompilatorach jest użycie __cxa_atexit() z interfejsu binarnego aplikacji C++ (ABI). Jest to odpowiednik POSIXowego atexit(). Oznacza to, że można zarejestrować specjalne programy obsługi, które będą wywoływane podczas zamykania programu. Gdy globalne konstruktory są wywoływane na początku aplikacji, jak opisano powyżej, istnieje również kod generowany przez kompilator, który rejestruje procedurę obsługi destruktora za pomocą __cxa_atexit(). Drugim sposobem jest przechowywanie wskaźników do destruktorów w specjalnych sekcjach .fini_array i .fini. Kompilator GCC użyłby tego sposobu, gdyby podano flagę -fno-use-cxa-atexit. W takim przypadku podczas kończenia aplikacji destruktory muszą być wywoływane w odwrotnej kolejności (od wysokiego adresu do niskiego). Ta metoda jest mniej powszechna, ale może być przydatna w mikrokontrolerach. Ponieważ w tym przypadku można dowiedzieć się, ile programów obsługi jest wymaganych w czasie kompilacji.

    Kod do wywoływania globalnych destruktorów obiektów wygląda następująco:

    Code: c
    Log in, to see the code


    Destruktory globalne są wymagane, aby móc ponownie uruchomić aplikację napisaną w C++. Większość RTOS-ów dla mikrokontrolerów uruchamia pojedynczą aplikację, która nie wymaga restartu. Dlatego w takich RTOS-ach globalne destruktory są puste, ponieważ nie powinny być używane.

    Kod z Zephyr RTOS do wywoływania globalnych destruktorów wygląda następująco:

    Code: c
    Log in, to see the code


    Gdy potrzebujesz aplikacji, które można ponownie uruchomić, musisz użyć systemów operacyjnych, w których zaimplementowana jest obsługa globalnych destruktorów. Embox udostępnia tę funkcję w C++ dla różnych platform, w tym mikrokontrolerów. Poniższy film przedstawia wielokrotne uruchamianie różnych aplikacji C++ na płytce STM32F769i-discovery.



    Operatory new i delete

    W kompilatorze GCC implementacja operatorów new oraz delete jest umieszczona w bibliotece libsupc++, a ich deklaracje znajdują się w pliku nagłówkowym. Można więc użyć implementacji new oraz delete z libsupc++, ale są one dość proste w wariancie podstawowym i można je również zaimplementować samodzielnie, na przykład za pomocą standardowych komend malloc oraz free lub ich analogów.

    Oto kod Ebox, który implementuje operatory new i delete (z wykorzystaniem tylko podstawowego C++):

    Code: c
    Log in, to see the code


    RTTI i wyjątki

    Jeśli rozwijana aplikacja jest prosta, wystarczy podstawowy C++ bez wyjątków i informacje o typie czasu wykonywania (RTTI). W takim przypadku funkcje te można wyłączyć za pomocą flag kompilatora [i]-no-exception i -no-rtti. Ale jeśli te funkcje C++ są wymagane, muszą zostać zaimplementowane. Jest to znacznie trudniejsze do zrobienia niż new/delete. Co więcej, w tym przypadku new/delete także muszą mieć bardziej złożone implementacje.

    Dobrą wiadomością jest to, że implementacje tych funkcji są niezależne od systemu operacyjnego i zostały już zaimplementowane w bibliotece libsupc++ w kompilatorze skrośnym. W związku z tym najłatwiejszym sposobem dodania ich obsługi jest użycie libsupc++. Prototypy umieszczane są w plikach nagłówkowych.

    Istnieją pewne wymagania, które wymagają użycia „standardowej biblioteki obsługi C++” z kompilatora skrośnego z własnym środowiskiem wykonawczym C++. Skrypt linkera musi mieć specjalną sekcję .eh_frame. A przed użyciem środowiska uruchomieniowego standardowa biblioteka obsługi C++ musi zostać zainicjowana adresem początku tej sekcji. Musi korzystać z biblioteki libunwind. Jest to biblioteka, która definiuje przenośny i wydajny interfejs programowania aplikacji w języku C (API) w celu określenia łańcucha wywołań programu.

    Oto kod z Embox do inicjalizacji libunwind:

    Code: c
    Log in, to see the code


    W przypadku architektury ARM wykorzystywane są inne sekcje z własną strukturą informacji – .ARM.exidx oraz .ARM.extab. Format tych sekcji jest zdefiniowany w standardzie Exception Handling ABI for the ARM Architecture (EHABI). .ARM.exidx to tabela indeksów, a .ARM.extab to tabela rzeczywistych pozycji wymaganych do obsługi wyjątku. Aby użyć tych sekcji do obsługi wyjątków, trzeba uwzględnić je w skrypcie linkera:

    Code: c
    Log in, to see the code


    Aby włączyć obsługę wyjątków, należy określić symbole początku i końca sekcji .ARM.exidx__exidx_start oraz __exidx_end. Więcej szczegółów na temat organizacji stosu można znaleźć w artykule "Jak działa śledzenie stosu w ARM".

    Standardowa biblioteka językowa (libstdc++)

    Samodzielna implementacja libstdc++

    Obsługa C++ obejmuje nie tylko składnię języka, ale także standardową bibliotekę libstdc++. To, podobnie jak składnia, może być podzielone na różne poziomy. Istnieją podstawowe elementy, takie jak wrappery libc dla przykładów pracy z łańcuchami lub wersja C++ setjmp() . Można je łatwo zaimplementować za pomocą standardowej biblioteki C. Są też bardziej złożone rzeczy, takie jak na przykład biblioteka szablonów standardowych (STL).

    Libstdc++ z kompilatora skrośnego

    Podstawowe rzeczy, takie jak te opisane powyżej, są zaimplementowane w Emboxie. Jeśli te funkcje są wystarczające, nie ma konieczności dołączania zewnętrznej biblioteki standardowej C++. Ale jeśli potrzebne są na przykład STL, najłatwiej jest użyć biblioteki i plików nagłówkowych z kompilatora skrośnego.

    Niemniej jednak istnieje kilka ważnych opcji dla kompilatora skrośnego. Na przykład standardowe arm-none-eabi-gcc wygląda tak:

    Code: c
    Log in, to see the code


    Przykład ten został zbudowany z opcją –with-newlib. Oznacza to, że potrzebuje ‘newlib’ jako standardowej biblioteki C.

    Aby zminimalizować obciążenie, Embox używa własnej implementacji biblioteki standardowej C. Oznacza to jednak również, że do obsługi środowiska uruchomieniowego konieczne jest zaimplementowanie warstwy kompatybilności z newlib.

    Kod w Embox, który implementuje jedną z niezbędnych, ale nieoczywistych części do obsługi standardowej biblioteki, wygląda następująco:

    Code: c
    Log in, to see the code


    Wszystkie elementy warstwy kompatybilności newlib dedykowane są do używania libstdc++ w kompilatorze skrośnym, co widać w Embox w folderze third-party/lib/toolchain/newlib_compat/.

    Zaawansowane wsparcie dla standardowych bibliotek std::thread oraz std::mutex

    Jeśli skompilujemy następujący kod:

    Code: c
    Log in, to see the code


    W projekcie mbed, kompilator zwróci następujący błąd:

    Code:
    namespace "std" has no member "mutex"


    Dzieje się tak, ponieważ istnieje jeszcze jeden istotny atrybut w kompilatorze skrośnym. Przyjrzyjmy się innemu komunikatowi wyjściowemu:

    Code:
    $ arm-none-eabi-gcc -v
    
    ***
    Thread model: single
    gcc version 9.3.1 20200408 (release) (GNU Arm Embedded Toolchain 9-2020-q2-update)


    Gdy GCC jest uruchomione z opcją pracy z pojedynczym wątkiem, obsługa wątków jest wyłączona w STL. Oznacza to, że np. std::thread lub std::mutex nie są dostępne. W związku z tym pojawią się problemy z budowaniem tak złożonych aplikacji C++ jak OpenCV. Innymi słowy, ta wersja biblioteki nie wystarczy do budowania aplikacji wymagających takich funkcji. Rozwiązaniem, które używane jest w Emboxie, jest zbudowanie skrośnego kompilatora gcc dla standardowej biblioteki z POSIXowym modelem wątków. W tym przypadku std::thread i std::mutex są zaimplementowane ze standardowymi pthread_ * i pthread_mutex_ *.

    Konfigurowanie Embox

    Powtórna budowa kompilatora jest najbardziej niezawodnym podejściem i zapewnia najbardziej kompletne i kompatybilne rozwiązanie. Jednocześnie zajmuje to dużo czasu i może wymagać dodatkowych zasobów, które nie są za bardzo dostępne w mikrokontrolerze. Dlatego nie zaleca się stosowania tej metody w każdym przypadku. Aby zapewnić najlepszy wybór obsługi C++ dla aplikacji użytkownika, do Emboxa dodano kilka klas abstrakcyjnych (interfejsów) z różnymi implementacjami:

    * embox.lib.libsupcxx – określa, której metody użyć do obsługi podstawowej składni języka.
    * embox.lib.libstdcxx – określa, której implementacji standardowej biblioteki użyć.

    Istnieją trzy możliwości dla libsupcxx:

    * embox.lib.cxx.libsupcxx_standalone – własna implementacja w Emboxie (tylko podstawowe funkcje).
    * third_party.lib.libsupcxx_toolchain – wykorzystanie libsupc++ z kompilatora hosta
    * third_party.gcc.tlibsupcxx – budowa libsupc++ ze źródeł

    Minimalna opcja może działać nawet bez standardowej biblioteki C++. Embox posiada implementację opartą na najprostszych funkcjach ze standardowej biblioteki C. Jeśli to nie wystarczy, można wybrać trzy warianty libstdcxx:

    * STLport.libstlportg – standardowa biblioteka zawierająca STL oparta na projekcie STLport. Nie wymaga budowania dedykowanego GCC. Ale projekt nie jest wspierany od 2008 roku.
    * lib.libstdcxx_toolchain – standardowa biblioteka C++ z kompilatora hosta
    * gcc.libstdcxx – pełna kompilacja biblioteki libstdc++ ze źródeł

    W ten sposób Embox z powodzeniem uruchamia tak złożone aplikacje pisane w C++ jak Qt czy nawet OpenCV.

    Podsumowanie

    Korzystanie z C++ jest bardzo wygodne, także na mikrokontrolerze. Większość funkcji C++ jest obsługiwana przez kompilator skrośny (libsupc++ i libstdc++). Ponadto C++ wymaga wsparcia ze strony systemów operacyjnych. Większość systemów operacyjnych dla mikrokontrolerów nie zakłada, że ​​aplikacja może zostać ponownie uruchomiona, dlatego nie implementują wywoływania globalnych destruktorów. Innym ograniczeniem jest obsługa wielowątkowości w libstdc++. Aby tego uniknąć, konieczna jest budowa kompilatora skrośny z obsługą modelu innego niż jednowątkowy. Embox RTOS rozwiązuje oba te problemy, umożliwiając wielokrotne uruchamianie aplikacji takich jak np. OpenCV na MCU.

    Źródło: https://www.embedded.com/running-advanced-c-software-on-mcus/

    Cool! Ranking DIY
    Can you write similar article? Send message to me and you will get SD card 64GB.
    About Author
    ghost666
    Translator, editor
    Offline 
    Fizyk z wykształcenia. Po zrobieniu doktoratu i dwóch latach pracy na uczelni, przeszedł do sektora prywatnego, gdzie zajmuje się projektowaniem urządzeń elektronicznych i programowaniem. Od 2003 roku na forum Elektroda.pl, od 2008 roku członek zespołu redakcyjnego.
    ghost666 wrote 10556 posts with rating 8910, helped 157 times. Live in city Warszawa. Been with us since 2003 year.
  • IGE-XAO
  • #2
    _lazor_
    Moderator of Designing
    Szkoda że im większą popularność zdobywają mikrokontrolery tym więcej powstaje środowisk typu "nic nie musisz wiedzieć by zacząć programować".

    Jednakże historia się po prostu powtarza. Gdy na popularności zdobywały poczciwe PC to zaczęto zwracać uwagę na koszt produkcji oprogramowania, a największym kosztem byli i są developerzy. Dzięki stworzeniu metod szybszego opracowania oprogramowania koszt tworzenia dramatycznie zmalał i to w sumie dobrze dla konsumenta końcowego.
    Braki w jakości są maskowane coraz szybszymi procesorami czy technologiami pokroju Hyper-threading.
    Oczywiście, mówię o aplikacjach typu biurowych, nie gdzie jednak wydajność ma znaczenie.

    I tutaj wchodzimy w podobny schemat, powstają środowiska gdzie przestaje mieć znaczenie wydajność a szybkość tworzenia. I to w sumie... Dobrze, jednak z dużym wykrzyknikiem.

    W przeciwieństwie do dużych sprzętów, jak serwery, PC, czy inne sprzęty z dużym OSem, przy mikrokontrolerach oprogramowanie sterowników jest kluczowe. Praktycznie pisanie na mikrokontrolery to pisanie sterowników. Dlaczego tak się dzieje? Gdyż mikrokontroler często nie jest urządzeniem ogólnego przeznaczenia i wymaga bycia amatorem hardware aby w pełni docenić jego możliwości
    Ilość dostępnych peryferiów jest wręcz przytłaczająca, każdy vendor tworzy swoje wersje podobnych peryferiów dodając coś od siebie, przez co stworzenie czegoś ogólnego, prawdopodobnie ograniczy dostęp do ciekawszych układów lub wymusi portowanie systemu na inny układ i dopisanie na modłę tego systemu brakujących sterowników... Lub tak jak robi to nordic semiconductor wspiera oficjalnie Zephyr RTOS i dopisuje ciągle drivery do swoich nowych układów.

    Dodajmy do tego że oprogramowanie można stworzyć na naprawdę wiele sposobów i każdy jest dobry. Tak samo jak wykorzystane języki programowania, każdy z najpopularniejszych języków z dobry kompilatorem na architektórę z którą chcemy pracować jest ok, ale tutaj jest największy problem czyli osoba która chce pracować przy tworzeniu oprogramowania.

    Bo czym jest tworzenie oprogramowania?
    Cóż pisanie oprogramowania na pewno nie polega na uderzaniu palcem w klawiaturę wpisując magiczne formułki, które potem są wykonywane przez bezmyślną maszynę.
    Pisanie oprogramowania polega na rozwiązywaniu problemów a język programowania jak i hardware jest tylko narzędziem.
    Często kryterium oprogramowania w świecie embedded jest rozmiar oprogramowania i jego wydajność, gdyż przekłada się to bezpośrednio na koszt produkcji urządzenia i w tym wypadku niestety osoba rozwiązująca tą łamigłówkę powinna uwzględnić jaki wpływ ma dany wzór projektowy, feature języka (oraz sam język) czy jeszcze jakaś platforma na samo rozwiązanie.

    Z narzędziami jest tak, że gdy osoba doświadczona zrobi dzieło, osoba mniej doświadczona zrobi... coś. C++ jest MOIM zdaniem błędnie polecany osobom niedoświadczonym, jest to język dobry ale złożony i bardzo łatwo robić głupie rzeczy. Weźmy przykład wyjątków:

    - Dobrze wykorzystane wyjątki nie mają większego wpływu na performance (a nawet mogą przyśpieszyć kod), wpływają na rozmiar kodu negatywnie, więc jeśli to jest kryterium istotne to może mieć wpływ.
    - Źle wykorzystane wyjątki, cóż czyli taki który odpala się częściej niż praktycznie nigdy, jest potężnym obciążeniem dla wydajności. To że można handlować wyjątek, nie oznacza że trzeba to wykorzystywać w normalnej pracy programu...

    A jako że Cpp ma featurów naprawdę dużo i ciągle są dodawane nowe... MOIM zdaniem nie jest to język przyjazny początkującym. Program zostanie wykonany, nawet ten najbardziej głupi.

    Inną sprawą jest architektura, naprawdę nim siądzie się do pisania kodu, warto się zastanowić jak to wszystko ma wyglądać. Znów, paradygmaty programowania nie zapewniają tego że program będzie dobry. Można pisać zarazem bardzo dobry kod w OOP jak i DOP czy proceduralnie, jak można napisać paździerz wykorzystując RTOS czy programując reaktywnie.
    Trzeba usiąść i się zastanowić jak poszczególne komponenty i zasoby będą ze sobą współpracować, aby po kilku dniach pisania nie okazało się że wszystko jest nieczytelne a modyfikacja fragmentu kodu powoduje nieoczekiwane błędy.

    Dlatego tworzenie oprogramowania to nie jest głównie walenie paluchami w klawiaturę a jakość programisty nie określa jak szybko uderza paluchami w klawiaturę. A wykorzystane narzędzia powinny być determinowane problemem a nie problem dostosowywany do narzędzi.


    Tak więc jeśli zdecydujemy się nauczyć platformy takiej jak powyżej to niestety możemy wpaść w pułapkę. Jako że poświęciliśmy mnóstwo czasu i energii to chcemy wykorzystywać nabytą wiedzę i narzędzia, często nie zastanawiając się do rozwiązywania jakich problemów zostało stworzone narzędzie i czy my w ogóle będziemy mieć z tym problemem styczność?
    I co możemy przeczytać na stronie Embox?
    "Embox main idea is using Linux software everywhere including MCUs."

    Ogólnie czuję że ktoś coś stworzył, zrobił jakąś ideę i... nie chciał rozwiązać żadnego problemu, tylko by problemy dostosowywać do narzędzia.




    Tak jest nieskładnie, za co przepraszam z góry. Późno jest i mi klawiszotok się uruchomił, a nie chce tego wszystkiego traktować deletem, a jak uważasz że jednak to powinienem uczynić zamiast publikować, możesz to napisać w odpowiedzi ;)
  • #3
    rafels
    Level 24  
    Nie jest tak, że kiedyś byli dobrzy programiści a teraz są źli. Wraz z postępem rośnie złożoność systemów informatycznych. Co za tym idzie nad jednym projektem potrafi pracować nawet kilkuset programistów. W związku z tym powstają nowe języki, paradygmaty programowania i konieczne jest przestrzeganie ustalonych zasad programowania. Wszystko po to żeby nowy programista był wstanie szybko się wdrożyć i dokonywać zmian w nie swoim kodzie.
    Nieraz widziałem osoby rodem z programowania proceduralnego których kod, był trudno zrozumiały dla innych i którym wydawało się ze ich rozwiązania są doskonałe. Ba, nawet oni sami po roku nie pamiętali jak to działa. Dlatego w takich projektach należy unikać błędów w stylu przedwczesna optymalizacja. Na to przyjdzie czas później. Kod ma być przede wszystkim zrozumiały, a nie super sprytnie napisany. Na optymalizajce może być czas później, gdy takie będą wymogi projektu. I to z głową, np. korzystając z profilingu.
  • #4
    _lazor_
    Moderator of Designing
    rafels wrote:
    Dlatego w takich projektach należy unikać błędów w stylu przedwczesna optymalizacja. Na to przyjdzie czas później.


    Z praktyki wiem, że na refaktor już czasu często nie starcza a najgorsze co można zrobić to wprowadzić workaround aby w późniejszym czasie go poprawić. Spoiler, później nigdy nie będzie na to czasu.

    rafels wrote:
    Nie jest tak, że kiedyś byli dobrzy programiści a teraz są źli. Wraz z postępem rośnie złożoność systemów informatycznych. Co za tym idzie nad jednym projektem potrafi pracować nawet kilkuset programistów.


    Właśnie z punktu widzenia tak dużego projektu piszę i po za językami, paradygmatami i przestrzegania zasad programowania, ważniejsze są dodatkowe narzędzia. Testy jednostkowe, testy regresyjne, narzędzia do statycznej analizy kodu, czy nawet głupie narzędzia do sprawdzania formatowania kodu naprawdę ułatwiają życie w tworzeniu oprogramowania.
  • IGE-XAO
  • #5
    rafels
    Level 24  
    Refaktoring to nieodłączna część wytwarzania oprogramowania. Wymagania często zmieniają się w trakcie projektu. Nie ma tak, że ktoś siada i pisze od początku do końca bez zmian. Natomiast co do przedwczesnej optymalizacji, to chodzi mi żeby kod był przede wszystkim przejrzysty i tak prosty jak się da. Często się trafiają "super" konstrukcje składniowe będące powodem dumy autora, których sam autor za jakiś czas nie rozumie. Trzeba raczej odstawić ego na bok i skupić na zasadach np. KISS itp.
    Testowanie na wielu poziomach powinno być standardem. Jednak wiem, że są firmy które jedynie testują manualnie albo nawet u klienta. Jak ich na to stać to ich sprawa.
  • #6
    katakrowa
    Level 22  
    _lazor_ wrote:
    Szkoda że im większą popularność zdobywają mikrokontrolery tym więcej powstaje środowisk typu "nic nie musisz wiedzieć by zacząć programować".


    Bo ja wiem. Ja tam jestem zadowolony z faktu, że w Atmel studio mogę napisać normalny program w pełni obiektowy na małego attiny2313 zamiast męczyć się w assemblerach czy kombinować w C.
    Przejrzystość kodu i wygoda pisania są na o wiele wyższym poziomie, a sam kod wynikowy chyba nawet mniejszy niż napisany w assemblerze bo inline'owanie funkcji i przeróżne automatyczne optymalizacje kompilatora wyłapują rzeczy, których zwykły śmiertelnik piszący a ASM zwyczajnie nie dostrzega, a nawet gdyby dostrzegł i zaimplementował to byłoby to jedynie "zaciemniaczem" kodu.
    Jak trzeba zrobić coś "mega optymalnego" zawsze wstawkę w ASM można wstawić. Na samej logice typu obsługa interfejsu, skoki między funkcjami / podprogramami assembler w dzisiejszych czasach nic nie daje - ani kod nie będzie mniejszy ani szybszy.

    Dzisiejsze kompilatory to już nie Keil sprzed 20 lat, który kompilował tak, że jak w funkcji nie było na końcu return to skompilowany kod wykonywał na pałę dalej :-)
  • #7
    _lazor_
    Moderator of Designing
    Użyte narzędzie jest tak dobre jak jego użytkownik. Jeśli ktoś jest biegły w assembly i ma duże doświadczenie to wie praktycznie to samo co robi kompilator + nie musi się trzymać ABI języka, co może mieć swoje plusy jak minusy. Jednak znów to zależy od umiejętności użytkownika.

    To samo z cpp i C. Kompilator nie będzie myślał za użytkownika. Patrz przykład z wyjątkami.
    Praktycznie jak we wszystkich dziedzinach ważna jest skala, tworzenie małego oprogramowania, przez mały zespół jest jak dla mnie czymś innym niż oprogramowanie które z założenia ma być np rozwijane przez wiele lat.

    Sam paradygmat programowania ma małe znaczenie, można napisać dobry obiektowy kod i można napisać bajzel typu uber obiekt od wszystkiego. Znów to zależy od umiejętności użytkownika.
    Niestety z doświadczenia wiem, że naprawdę mały procent ludzi deklarujących się jako ludzi mówiących że potrafią cpp i OOP naprawdę potrafią coś w tym sensownego stworzyć. Bo nie ważne że ktoś zna jakiś feature języka, ważne jest to że użytkownik wie kiedy go użyć a kiedy jednak się powinno z nim powstrzymać.
    W sumie jak pisał @rafels
    rafels wrote:
    Często się trafiają "super" konstrukcje składniowe będące powodem dumy autora, których sam autor za jakiś czas nie rozumie. Trzeba raczej odstawić ego na bok i skupić na zasadach np. KISS itp.
  • #8
    katakrowa
    Level 22  
    _lazor_ wrote:
    Użyte narzędzie jest tak dobre jak jego użytkownik. Jeśli ktoś jest biegły w assembly i ma duże doświadczenie to wie praktycznie to samo co robi kompilator + nie musi się trzymać ABI języka, co może mieć swoje plusy jak minusy. Jednak znów to zależy od umiejętności użytkownika.


    Generalnie tak. Mały zespół + assembler OK jednak duży zespół + assembler zamiast "wstawki" już nie jest ok o ile nie tworzymy kodu na potrzeby demosceny.
    Oczywiste jest także, to że jak ktoś umie ASM a nie umie C to piej niech pisze w ASM jednak. Takie obiektowe CPP ma wyższość nad ASM chociażby ze względu na dużo większą łatwość przenoszenia na inne architektury. Dzisiaj assembler jest trochę jak kos maszynowy w latach 70'tych / 80'tych... Wciąż jest podstawą ale nikt rozsądny już w tym nie pisze.
    Wyższość języków wysokiego poziomu nas ASM jest właśnie taka jak ASM nad kodem maszynowym. Zalety w mnie nie do podważenia:
    - czytelność ;
    - przenaszalność na inne platformy ;
    - dostępność dla szerszego grona programistów ;
    - uniwersalność języka.

    Oczywiście mamy dziś różne "wirtualne assemblery" kompilowane do niemal wszystkich platform (jak chociażby WebASM), ale póki co w dziedzinie mikrokontrolerów z takim czymś się nie spotkałem.

    Dziś na rynku mamy jeszcze sporo specjalistów w ASM ale nie da się ukryć, że jest to specjalizacja wymierająca - co podyktowane jest tym co pisałem wcześniej... Czyi to, że ten język nie wnosi już żadnych zalet w końcowym rozwiązaniu.
    Zapewne ASM jak i kod maszynowy w bardzo wąskich specjalizacjach wciąż pozostaną żywe bo to jednak podstawa i póki co nic nie wskazuje na to aby miało się coś zmienić... W głównym nurcie jednak pozostanie jedynie warstwą abstrakcji dla kompilatorów a człowiek nie będzie musiał tego ani rozumieć ani dotykać.
  • #9
    _lazor_
    Moderator of Designing
    Osobiście nie piszę w assemblerze, jednak znajomość tego języka znam na każdej platformie na której piszę (oraz pipeline). Tak więc pisząc aktualnie na x86, używam takiego narzędzia:
    https://llvm.org/docs/CommandGuide/llvm-mca.html

    A używa się takich narzędzi gdyż kompilatory są głupie i nie do końca potrafią optymalizować jeśli nie napisze się odpowiedniego kodu. Jednak w sumie w samym assemblerze już nikt faktycznie nie pisze.
    Jednocześnie czy to assembler, cpp czy c.


    Jednocześnie OOP nie jest czymś nadzwyczajnym i naprawdę każdy paradygmat jest dobry, gdy użytkownik używa go z głową.
    Nauczenie się jednego paradygmatu i trzymanie się go kurczowa to jak używanie młotka do wszystkiego czy przybijania gwoździa czy wkręcania wkrętów.
  • #10
    krzysiek_krm
    Level 40  
    katakrowa wrote:
    Wyższość języków wysokiego poziomu nas ASM jest właśnie taka jak ASM nad kodem maszynowym.

    Moim zdaniem ta analogia jest cokolwiek nietrafiona. Asembler to jest to samo co kod maszynowy, tyle że (jako - tako) akceptowalny przez człowieka, ze względu na pewne mniej - więcej zrozumiałe mnemoniki.
    Mówiąc szczerze, nie słyszałem żeby ktokolwiek, kiedykolwiek tworzył programy w języku maszynowym, w sensie - bez mnemoników asemblera, samemu generując op-cody, itp, itd.
    katakrowa wrote:
    Takie obiektowe CPP ma wyższość nad ASM chociażby ze względu na dużo większą łatwość przenoszenia na inne architektury.

    Przecież C (nieobiektowy) też jest łatwy do przenoszenia.
    Coś mi się wydaje, że tu i ówdzie używanie C++ stało się jakąś manią i fetyszem
    _lazor_ wrote:
    to jak używanie młotka do wszystkiego czy przybijania gwoździa czy wkręcania wkrętów

    dla miłośników walenia młotkiem wymyślono śruby wbijane a nie wkręcane - to żart oczywiście ale trochę go ilustruje Twoja wypowiedź
    _lazor_ wrote:
    Ogólnie czuję że ktoś coś stworzył, zrobił jakąś ideę i... nie chciał rozwiązać żadnego problemu, tylko by problemy dostosowywać do narzędzia.
  • #11
    katakrowa
    Level 22  
    krzysiek_krm wrote:
    Coś mi się wydaje, że tu i ówdzie używanie C++ stało się jakąś manią i fetyszem


    Absolutnie nie. C++ nigdy nie był i nie jest moim głównym językiem programowania. Używam go tylko i wyłącznie hobbystycznie w elektronice i szczerze mówiąc absolutnie nie chciałbym w nim robić zawodowych rzeczy. Nie zmienia to faktu, że dostrzegam jego zalety w porównaniu do ASM czy C.
  • #13
    krzysiek_krm
    Level 40  
    _lazor_ wrote:
    Jak software będzie kiepsko zaprojektowany to czy to C, Cpp czy to ASM to i tak będzie działać byle jak a jego rozwijanie będzie wielkim bólem.

    Czyli kluczem do sukcesu (jak można było się domyślić) jest umiejętność budowy porządnych algorytmów a nie znajomość magicznego języka - "mam kompilator C++ i nie zawaham się go użyć".
  • #14
    khoam
    Level 40  
    krzysiek_krm wrote:
    Czyli kluczem do sukcesu (jak można było się domyślić) jest umiejętność budowy porządnych algorytmów a nie znajomość magicznego języka - "mam kompilator C++ i nie zawaham się go użyć".

    Dla jednych magiczny, dla drugich bardzo użyteczny w implementacji tych "porządnych" algorytmów ;)
  • #15
    katakrowa
    Level 22  
    _lazor_ wrote:
    Jak software będzie kiepsko zaprojektowany to czy to C, Cpp czy to ASM to i tak będzie działać byle jak a jego rozwijanie będzie wielkim bólem.


    krzysiek_krm wrote:
    Czyli kluczem do sukcesu (jak można było się domyślić) jest umiejętność budowy porządnych algorytmów a nie znajomość magicznego języka - "mam kompilator C++ i nie zawaham się go użyć".


    W tego typu dyskusjach zawsze pojawia się argument, że ktoś wykona swoją robotę źle bo nie umie. Takie podejście jest całkowicie bez sensu.
    Należy założyć, że stawiamy przed komputerem dwóch specjalistów doskonale znających swoje języki. Tylko w takiej sytuacji można rozmawiać o wyższości lub niższości narzędzia w takim czy innym zastosowaniu. Argumentowanie, że dobry programista w ASM zrobi lepiej niż zły w C/C++ jest po pierwsze oczywiste, a po drugie bez sensu w kontekście porównywania języków jako różnych narzędzi.

    W takim kontekście ASM praktycznie nie ma zastosowań, w których jest lepszy od C++, w którym jak wiadomo w razie potrzeby można robić wstawki kodu maszynowego lub ASM.
    Kod wynikowy z C++
    - ani nie jest wolniejszy od kodu w ASM;
    - nie jest większy od kodu w ASM ;
    - sam język daje możliwość wygodnego i szybszego zaprogramowania tego samego co w ASM z tym samym efektem końcowym ;

    ASM to brzeszczot a C++ to piła elektryczna. Jak się ktoś uprze to grube drzewo faktycznie można ściąć jednym i drugim ale jednak elektryczną zdobimy to szybciej, a wybór narzędzia wydaje się być oczywisty.
    Gdyby ktoś porównywał ASM z JAVA, Python czy PHP to faktycznie liczba argumentów za ASM zdecydowanie by wzrosła jednak C++ jest z jednej strony bardzo rozbudowanym językiem, a z drugiej nie nakłada żadnych ograniczeń na bezpośredni dostęp do sprzętu. Nie wspominając o tym, że jest zwyczajnie bardziej przyswajalny.

    Jak wystawimy na wyścigi fiata 126p i BMW to faktycznie nadal wszystko zależy od kierowcy ale jak do oby wsadzimy sensownych kierowców to jednam maluch z BMW nie ma szans i wydaje mi się, że jest to bezdyskusyjne.
  • #16
    _lazor_
    Moderator of Designing
    katakrowa wrote:
    W tego typu dyskusjach zawsze pojawia się argument, że ktoś wykona swoją robotę źle bo nie umie.

    Dlaczego? Aktualnie w projekcie mamy ponad 200 osób, cpp + standard 5G NR. Zakładanie że każda osoba z tych 200 zrobi kod dobrze jest utopią. Jest za to wprowadzony system testów jednostkowych, review, guideline wewnętrznych, sporo narzędzi do statycznej analizy, dodatkowo porównujemy się do modeli matematycznych.
    Nadal jest trochę bugów, ale naprawdę ciężko coś wprowadzić nowego, a nowi ludzie można rzecz że łatwiej się wdrażają do standardy 5G NR.

    Jesteśmy tylko ludźmi a ludzie mają ogromną tendencję do popełniania błędów, i by coś w skali systemu działało to takie musi być założenie. Dlatego TDD w praktyce się sprawdza.

    Jeszcze trzeba nie zapomnieć że dany problem tymi samymi narzędziami można rozwiązać na setki sposobów i każdy będzie się od siebie różnił. To jest dopiero armagedon gdy dwóch specjalistów ma dwie wizje rozwiązania tego samego problemu.


    Nie wiem czemu się przyczepiłeś do tego ASM, ale śmiem wątpić byś miał gdzieś rzeczywiste porównanie implementacji cpp vs ASM a jedynie bardzo nietechniczne przepychanie się słowne. Jak chcesz udowodnić to technicznie podaj wartości liczbowe a nie słowne.
    Są to języki stworzone do trochę innych celów i mające całkowicie inne założenia. To nie jest porównanie 126p i BMW tylko motorówki i motoru. Niby się poruszają, ale jednak są stworzone do całkowicie innego terenu.
  • #17
    katakrowa
    Level 22  
    _lazor_ wrote:
    ale śmiem wątpić byś miał gdzieś rzeczywiste porównanie implementacji cpp vs ASM a jedynie bardzo nietechniczne przepychanie się słowne. Jak chcesz udowodnić to technicznie podaj wartości liczbowe a nie słowne.


    Wydaje mi się, że Twoja wypowiedź już jest nieco niegrzeczna takie trochę argumentowanie ad personum... To tak jak bym napisał, iż przypuszczam, że z tych 200 zatrudnionych w Waszym projekcie osób to właśnie Ty jesteś tym co robi te błędy stąd tyle obaw o umiejętności programistów. Dlatego jak się nie wie to lepiej nie pisać. Za młodu bardzo dużo zajmowałem się grafiką 3D gdzie właśnie w tych dwóch językach były implementacje większości algorytmów. Wymyślaliśmy sobie konkursy kto więcej klatek w na sekundę wyrenderuje i inne podobne zabawy. Może to i było dawno ale ogólna istota obu języków do dziś nie uległy zmianie.

    Inie trzeba żadnych pomiarów ani badań robuć żeby wiedzieć, że w te same rzeczy ASM pisze się wolniej nić w C/C++. Jesli tego nie wiesz to już na to nic nie poradzę. To tak jabyś na siłę chciał udowodnić, że jednak maluch wyprzedzi to BMW, o którym pisałem.
  • #18
    _lazor_
    Moderator of Designing
    Dlaczego personalna? Rzeczy techniczne są mierzalne, jeśli mówisz że:
    katakrowa wrote:
    - ani nie jest wolniejszy od kodu w ASM;
    - nie jest większy od kodu w ASM ;


    Zwarzywszy że sam standard języka wymaga wprowadzenia dość sporo elementów przed wykonaniem głównego programu to już powoduje narzut względem ASM.

    katakrowa wrote:
    Inie trzeba żadnych pomiarów ani badań robuć żeby wiedzieć, że w te same rzeczy ASM pisze się wolniej nić w C/C++.


    Tego idealnie nie podważam i nie mówię by pisać w ASM. Problemem jest to że fizycznie kod cpp/c nie może być mniejszy, właśnie przez zapewnianie standardu języka i ograniczenia narzucane przez ABI. Jednocześnie to są bardziej purystyczne rozważania, niż coś praktycznego.
    Zwłaszcza gdyby chciało się pisać na procesorach super skalarnych, lepiej poświęcić trochę miejsca na ABI i zaspokojenie wymagań standardu niż bawić się w pisanie ASM.

    katakrowa wrote:
    Za młodu bardzo dużo zajmowałem się grafiką 3D gdzie właśnie w tych dwóch językach były implementacje większości algorytmów.


    Przetwarzanie w telekomunikacji nie aż tak bardzo się różni od przetwarzania grafiki 3D, jednak procesory idą do przodu i np intel wprowadza nowe ISA (np AVX512), których kompilator nie wykorzysta bezpośrednio. Wtedy nie ma innej możliwości jak używać przygotowanych Intrinsicsów. Popatrz ile tego jest i nie są one w większości dostępne bezpośrednio przez kompilator:
    https://software.intel.com/sites/landingpage/IntrinsicsGuide/#techs=AVX_512

    Nawet małe procki pokroju cortex-m4 ma instrukcje SIMD, które nie są dostępne z poziomu kompilatora a Intrinsicsów.
    https://www.keil.com/pack/doc/CMSIS/Core/html/group__intrinsic__SIMD__gr.html

    Jednocześnie to co pisze powyżej nie jest czymś co jest must have dla każdego developera. Jak pisałem w pierwszym poście wszystko zależy od wymagań projektowych i nie zawsze taka wiedza czy wykorzystanie pewnych cech procka jest potrzebna.
    Jednocześnie by wycisnąć maksymalne możliwości z układu to już dość spora znajomość mechanizmów jest wymagana. Nie tylko samego hardware ale i z wykorzystywanego języka i toolchaina do niego.



    katakrowa wrote:
    To tak jak bym napisał, iż przypuszczam, że z tych 200 zatrudnionych w Waszym projekcie osób to właśnie Ty jesteś tym co robi te błędy stąd tyle obaw o umiejętności programistów.


    Tworzenie całego środowiska testowego, aby zminimalizować błędy tworzone przez jwdnego lazora? Zważywszy ile idzie czasu na rozwijanie środowiska testowego to gwarantuje że nie chodzi o jednego lazora ;)
    Jednocześnie wiem z praktyki że lepiej poświęcić 3 razy więcej czasu z napisaniem porządnego środowiska testowego niż potem łatać bugi (nie żeby się nie zdarzały w ogóle, ale liczba jest stanowczo mniejsza).
  • #19
    katakrowa
    Level 22  
    @_lazor_ faktycznie różnica, jest bardziej subtelna niż rozgorzała dyskusja. Myślę, że powyższa wymiana argumentów w zupełności wystarcza by przedstawić różnice w spojrzeniu na poruszoną kwestię.

    Dziękuję za wymianę poglądów i pozdrawiam.
  • #20
    jarek_lnx
    Level 43  
    _lazor_ wrote:
    Nie wiem czemu się przyczepiłeś do tego ASM, ale śmiem wątpić byś miał gdzieś rzeczywiste porównanie implementacji cpp vs ASM a jedynie bardzo nietechniczne przepychanie się słowne. Jak chcesz udowodnić to technicznie podaj wartości liczbowe a nie słowne.
    Tak czytam ten wątek i też nie rozumiem dlaczego on to robi, C++/C ma nad ASM rzeczywistą przewagę w wielu kwestiach i nie potrzeba mu przypisywać wyimaginowanej zalety w postaci równej/lepszej wydajności.

    Choć ostatnio użyłem ASM kilka lat temu i choć zrobiłem to metodą na lenia, optymalizacja szybkości wykonania fragmentu kodu (przerwania) była naprawdę znacząca usunąłem 60%. Wyglądało to tak, używałem gcc (na AVR), z kodu w C skompilowałem do ASM, później poprawiny kod w ASM zastąpił funkcję w C z której powstał (prościej zamienić całą funkcję/plik niż zrobić wstawkę asemblerową )
    Jak wyglądał ten kod? gcc na początku wrzucił na stos wszystkie rejestry a na koniec je zdejmuje ze stosu, kod był banalnie prosty używał tylko dwóch rejestrów, więc 60% kodu przerwania, które przerzucało nieużywane rejestry, mogłem usunąć. To skrajny przypadek ale pokazuje że kompilator w niektórych miejscach generuje "durny" kod, tak jest prościej i szybciej dla programisty piszącego kompilator :)
    Nie ma w tym nic złego, zawsze możemy zastosować szybszy mikrokontroler, czas programisty jest cenniejszy niż czas procesora :)
  • #21
    _lazor_
    Moderator of Designing
    @jareklnx Trochę brzmi jakbyś grzeszył, jak można zaorać kontekst prockowi? :D cortex-m ma to zrobione implicite w przerwaniach, ale już taki cortex-A ma identyczne rozwiązanie gdzie trzeba w przerwaniu skopiować stan rejestrów a potem przywrócić.
  • #22
    jarek_lnx
    Level 43  
    Ja się bawiłem małym prymitywnym prockiem, ale że w cortex-A trzeba to zrobić "ręcznie", to mnie zaskoczyłeś.
  • #23
    _lazor_
    Moderator of Designing
    cortex-a nie ma takich wektorów przerwań jak cortex-m. Bawiłem się bare metal na malinie i powiem Tobie - nie warto.
    To jednak są układy przeznaczone do aplikacji i naprawdę nie są przeznaczone do jakiejś sensownej interakcji z otoczeniem.

    Ale rozwiązania gdzie jest cortex-A i cortex-m na jednym SoC uważam za super rozwiązania, ale jak zwykle jest więcej rzeczy dookoła by się w takie rozwiązania zagłębić :(
  • #24
    krzysiek_krm
    Level 40  
    jarek_lnx wrote:
    Nie ma w tym nic złego, zawsze możemy zastosować szybszy mikrokontroler, czas programisty jest cenniejszy niż czas procesora :)

    A jak w grę wchodzą miliony sztuk - nadal jest prawdziwa ta relacja ?
  • #25
    _lazor_
    Moderator of Designing
    @krzysiekkrm znów zależy od rozmiaru projektu :D Są programiści co coś zrobią ale w sposób szybki i stosunkowo tani, lub optymalny ale nie za tanio. Pytanie co będzie droższe i to jest jedna z zmiennych przy doborze uC.
    A powiem Tobie że firmy decydują się nie rzadko na bardziej rozbudowane droższe układy, by rozwój był łatwiejszy i przy tym tańszy.
  • #26
    rafels
    Level 24  
    krzysiek_krm wrote:
    jarek_lnx wrote:
    Nie ma w tym nic złego, zawsze możemy zastosować szybszy mikrokontroler, czas programisty jest cenniejszy niż czas procesora :)

    A jak w grę wchodzą miliony sztuk - nadal jest prawdziwa ta relacja ?


    Czasem tak czasem nie. Wszystko zależy co da większy zysk 😃 Np. Bardziej może się liczyć czas wejścia na rynek lub czas dodania nowych funkcji.