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

Program wykrzacza się, gdy zmienna nie jest zadeklarowana jako static.

lupin22 02 Mar 2021 11:08 1443 31
  • #1 19291592
    lupin22
    Poziom 10  
    Cześć,

    programowanie embedded nie przestaje mnie zaskakiwać.

    Mam program, ogólnie dość rozbudowany, na procesor xmega. Program między innymi odtwarza dźwięk.

    Zajmując się czym innym chciałem sobie czasowo mrugać ledem, więc do sprawdzania czasu korzystam z funkcji millis(), która wygląda tak:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod


    Wydaje mi się, że jest to dość klasyczne rozwiązanie. Zmienna elapsed_time jest typu long i jest po prostu inkrementowana w przerwaniu o częstotliwości 1kHz.

    Kod testowy wygląda tak:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod


    Niestety po kilkunastu sekundach zaczyna mi się "rozjeżdżać" dźwięk - najpierw robi się tak, jakby był odtwarzany z dużego pomieszczenia, a potem całkowicie się wykrzacza. Niestety nie znam się za bardzo na jego obróbce, ale to by sugerowało chyba wprowadzanie jakichś opóźnień, chociaż nie kieruję się tym za bardzo. Ogólnie związek może być bardzo luźny, ale ze względu na to, że jest on odtwarzany cały czas, to on jest najbardziej oczywistym objawem.

    Szukałem problemu długo, bo jaki może być związek? Koniec końców udało mi się replikować problem powtarzalnie, a także go uniknąć.

    Jeśli
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    zamienię na
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    problem znika.

    Jaka może być tego przyczyna? Tak szczerze mówiąc nie mam zielonego pojęcia jakie może to mieć znaczenie... Czy to kompilator coś optymalizuje? Czy szukać tutaj problemu w pamięci RAM? Ale jakim cudem taka drobna zmiana mogłaby doprowadzić do jakiegoś wycieku pamięci?

    Z góry bardzo dziękuję za pomoc. Już się czułem mocniejszy w tym programowaniu, ale jednak zawsze człowiek szybko jest sprowadzany do pionu jakimś problemem tego typu, który zupełnie wykracza poza moje zrozumienie. A że jest to projekt komercyjny i nie mam możliwości późniejszej aktualizacji firmware, to zależy mi szczególnie na zrozumieniu go, a nie tylko uniknięciu.
  • #2 19291644
    JacekCz
    Poziom 42  
    Mam nieoficjalne przeczucie, że problem jest *) w części kodu, której tu nie podajesz.

    Zgadywanek może być wiele, np kosztowna funkcja flicker zajmująca bardzo dużo stosu itd... bo małą pewnie nie jest, jeśli mowa o dźwięku.
    Albo że "przejeżdża bufory".
    C/C++ jest pełne UB undefined behaviours, czyli błędów jakie istnieją a nie zawsze się ujawniają.
    Jeśli tak drobna zmiana "leczy", zdecydowaniu tu mi na UB wygląda.
    Przypomniał mi się taki żart zawodowy, że problematyczna jest nieparzysta ilość błędów.

    *) lub w zależnościach kontekstowych tego co podajesz, i tego co nie podajesz

    ps. słowo wyciek pamieci rozumiesz nieprawidłowo / tu nie zachodzi
  • #3 19291678
    lupin22
    Poziom 10  
    JacekCz napisał:
    Mam nieoficjalne przeczucie, że problem jest *) w części kodu, której tu nie podajesz.

    Wiem, że to się zawsze nasuwa, ale tego kodu tam jest tyle, że jakbym go wypisał to raczej by sytuacji nie rozjaśniło.

    JacekCz napisał:

    Zgadywanek może być wiele, np kosztowna funkcja flicker zajmująca bardzo dużo stosu itd... bo małą pewnie nie jest

    Raczej nie. Jest to funkcja debugowa, żebym widział, co się dzieje. Dałem tam sobie reset watchdoga, avrową funkcję:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    i to samo.

    Także to odpada.

    JacekCz napisał:

    jeśli mowa o dźwięku
    C/C++ jest pełne UB undefined behaviours, czyli błędów jakie istnieją a nie zawsze się ujawniają.

    Nie wiem, czy tego bym się tutaj spodziewał. Odtwarzam bezpośrednio z próbek, steruję tylko głośnością mnożąc próbki razy 0-127 i dzieląc przed 128. Próbki podawane są na DAC. Problem raczej nie dotyczy samego dźwięku, tylko jest najbardziej oczywisty ze względu na to, że i na niego oddziałuje.

    JacekCz napisał:

    ps. słowo wyciek pamieci rozumiesz nieprawidłowo / tu nie zachodzi

    Całkiem możliwe, ale żeby przepełnić ram to jedyne, co mi przyszło do głowy, to że gdzieś tutaj się przelewa stack. Zwłaszcza, że problem potrzebuje kilkunastu sekund, żeby się rozkręcić. Rzadko operuję tymi stwierdzeniami więc przepraszam tutaj za nieprawidłowe użycie.

    Sprawdziłem właśnie poziomy optymalizacji - był O1. Zmieniłem na O2 i o dziwo nieważne, czy jest static, czy nie ma to wydaje się działać. To by sugerowało, że reszta kodu jest tu bez większego znaczenia. Jaki może być powód takiego zachowania? Kwestia wykorzystania jakichś rejestrów może w celu optymalizacji?
  • #4 19291679
    JacekCz
    Poziom 42  
    lupin22 napisał:
    A że jest to projekt komercyjny


    Zdanie z przykrością uruchamia we mnie gorzkie nuty, o niskiej jakości oprogramowania embedded. Od ekspresu, który melduje brak kawy LUB brak wody, ale zawiesza się na twardo gdy zachodzą OBA warunki, po softwarowo spadające Airbusy
  • #5 19291688
    tadzik85
    Poziom 38  
    Sorry,, źle patrzyłem. o promocji typu wyniku operacji matematycznej słyszałeś?
  • #6 19291689
    lupin22
    Poziom 10  
    JacekCz napisał:
    lupin22 napisał:
    A że jest to projekt komercyjny


    Zdanie z przykrością uruchamia we mnie gorzkie nuty, o niskiej jakości oprogramowania embedded. Od ekspresu, który melduje brak kawy LUB brak wody, ale zawiesza się na twardo gdy zachodzą OBA warunki, po softwarowo spadające Airbusy

    Jakość jakością, moje oprogramowanie chociaż pewnie nie napisane najpiękniej, to jednak od ponad 2 lata działa bezbłędnie na setkach sztuk. Bardzo dużą uwagę przywiązuję do tego, żeby wiedzieć, co się dzieje w kodzie, dlatego ten problem tak mnie martwi. Bo chociaż niby potrafię go zniwelować, to nie rozumiem dlaczego.

    tadzik85 napisał:
    Skoro to projekt komercyjny to kolego sięgnij do podręcznika bo chodzi tu o podstawy.

    Błąd jest tak oczywisty, że aż razi w oczy.

    To bardzo proszę o wyjaśnienie.
  • #8 19291714
    lupin22
    Poziom 10  
    tadzik85 napisał:
    if (millis() - time > 1000)


    źle. bardzo źle.


    millis() zwraca typ long, time jest typu long. Jedyne, co mi jeszcze przychodzi do głowy to:
    if (millis() - time > (long)1000)

    chociaż wydaje mi się to niepotrzebne, no i koniec końców sprawdziłem również tak - nic to nie zmienia.
  • #9 19291729
    tadzik85
    Poziom 38  
    co z wartościami ujemnymi?
  • #10 19291731
    JacekCz
    Poziom 42  
    lupin22 napisał:
    moje oprogramowanie chociaż pewnie nie napisane najpiękniej, to jednak od ponad 2 lata działa bezbłędnie na setkach sztuk.


    Dopóki nie zmienisz czegoś w psychologii, będziesz sobie udowadniał bezbłędność, to ciągle poziom początkującego.
    Titanica też budowali optymiści, ja w inżynierii wolę pesymistów.
    Niech optymiści idą do marketingu.

    A samo zdanie powinno brzmieć: bez ujawnienia błędu, a nie ujawnienie błędu w C/C++ jest bardzo łatwe, wręcz naturalne.


    ps. literał long się pisze 1000l lub 1000L. Rzutowanie to coś innego, choć w TYM przypadku ok - w wielu innych da odmienne efekty.
    Wolałbym bym, jakby komercyjne embedded pisali ludzie nie dający się zagiąć w zakresie języka (i nauka na desktopie)
  • #11 19291744
    lupin22
    Poziom 10  
    JacekCz napisał:
    lupin22 napisał:
    moje oprogramowanie chociaż pewnie nie napisane najpiękniej, to jednak od ponad 2 lata działa bezbłędnie na setkach sztuk.

    Dopóki nie zmienisz czegoś w psychologii, będziesz sobie udowadniał bezbłędność, to ciągle poziom początkującego.

    Nigdzie tego nie neguję. Nie wiem jednak co za znaczenie ma, że jest to fragment programu komercyjnego.

    tadzik85 napisał:
    co z wartościami ujemnymi?

    Wartość nie może być ujemna. Millis() będzie zwracał coraz większą wartość, nie może się cofnąć. A jeśli nawet by się zatrzymał, to wyjdzie 0, bo przecież time jest nadpisywany za każdym razem.
  • #12 19291756
    tadzik85
    Poziom 38  
    lupin22 napisał:
    tadzik85 napisał:
    co z wartościami ujemnymi?

    Wartość nie może być ujemna. Millis() będzie zwracał coraz większą wartość, nie może się cofnąć. A jeśli nawet by się zatrzymał, to wyjdzie 0, bo przecież time jest nadpisywany za każdym razem.
    To tylko wróżki wiedzą.
  • #13 19291771
    lupin22
    Poziom 10  
    tadzik85 napisał:
    lupin22 napisał:
    tadzik85 napisał:
    co z wartościami ujemnymi?

    Wartość nie może być ujemna. Millis() będzie zwracał coraz większą wartość, nie może się cofnąć. A jeśli nawet by się zatrzymał, to wyjdzie 0, bo przecież time jest nadpisywany za każdym razem.
    To tylko wróżki wiedzą.


    No dobrze, jeśli będzie wartość ujemna, to się ciało ifa nie wykona, bo nie spełni warunku. Typ long jest signed, więc jaki będzie z tym inny problem?
  • #14 19291778
    tadzik85
    Poziom 38  
    I czasówka pójdzie w diabły.
  • #15 19291795
    lupin22
    Poziom 10  
    Czy możesz wyjaśnić dlaczego? I dlaczego miałoby to taki efekt, że rozkraczy się reszta programu, która z millis() nie korzysta (to wywołanie w ifie jest jedyne). I dlaczego zmiana typu zmiennej na static problem rozwiązuje?

    Dodano po 1 [godziny] 20 [minuty]:
    PS. Odpowiadam tutaj na post, który zniknął.

    No cóż, mam nadzieję w takim razie, że są tutaj na forum osoby, które będą w stanie zidentyfikować problem. Byłbym za to bardzo wdzięczny, bo siedzę już nad tym długo, a i samo dojście do wyizolowania problemu łatwe nie było. Zauważyłem, że inne funkcje również notoryczne korzystają z funkcji millis() w takim samym układzie, jak tutaj (z tą różnicą, że w tych funkcjach zwykle ta zmienna przechowująca w funkcji czas lokalnie jest siłą rzeczy typu static long) i potem krok po kroku sprawdzałem co działa, a co nie.

    Dla krótkiego podsumowania, co się dzieje:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Powoduje trudne do określenia problemy przy wykonywaniu programu, które nasilają się bardziej, im dłużej działa program. "Rozwiązaniem" problemu są:

    1. Zamiana long time na static long time.
    lub
    2. Zmiana poziomu optymalizacji z O1 na O2, pozostawiając zmienną long time tak, jak jest wyżej, bez dodawania static.

    Co może być powodem takiego zachowania? Czy to może kwestia związana z użyciem rejestrów mikrokontrolera przez kompilator? W jaki sposób takie stosunkowo niewinne operacje mogą powodować kaskadowe problemy? Chciałbym w miarę możliwości uwzględniać te kwestie przy dalszym programowaniu.
  • Pomocny post
    #16 19292007
    ex-or
    Poziom 28  
    IMHO przyczyna leży całkiem gdzie indziej. Może procek się nie wyrabia. W przypadku zmiennej globalnej, na etapie konsolidacji jej adres jest znany, a więc dostęp może być przez adresowanie bezpośrednie. W przypadku zmiennej na stosie prawdopodobnie (nie wiem, domyślam się, nie chce mi się sprawdzać) zachodzi adresowanie pośrednie. Być może z dodatkowym wykorzystaniem ogólnych rejestrów po drodze. A więc zapewne trwa to dłużej. Być może tych kilka taktów więcej jest źdźbłem które przepełnia czarę.
  • #17 19292177
    lupin22
    Poziom 10  
    ex-or napisał:
    IMHO przyczyna leży całkiem gdzie indziej. Może procek się nie wyrabia.

    Do mojej glównej pętli programu mogę wrzucić _delay_ms(2000) i nic się złego nie dzieje. Nominalnie wykonuje się ona w około 7ms.

    Dźwięk odtwarzany i obrabiany jest całkowicie w przerwaniach. Co więcej, udało mi się tak tę obróbkę zoptymalizować, że reszta programu zaczęła się wykonywać szybciej, co zauważyłem głównie po tym, że czas nalicza się teraz poprawnie, wcześniej był lekko opóźniony, także te przerwania naliczające milisekundy musiały czekać w kolejce, aż próbki zostaną przygotowane.

    EDIT: Chociaż sprawdzę to, rzeczywiście włączenie lepszej optymalizacji może sugerować, że gdzieś brakuje mocy. Niby przerwania działają szybciej teraz, no ale kto wie.

    Dodano po 1 [godziny] 3 [minuty]:

    ex-or napisał:
    Może procek się nie wyrabia.

    Chyba masz rację. Jestem zaskoczony, bo wprowadziłem kilka dość istotnych optymalizacji i jak już wspomniałem, niektóre funkcje, które miały wcześniej określony czas wykonywania, typu błyśnięcie ledem przez x ms zaczęły wykonywać się szybciej, więc tego nie podejrzewałem.

    Ale miałem trochę zapasu zegara jeszcze, korzystałem do tej pory z 24MHz (ze względu na USB), więc zmieniłem źródło na 2MHz z PLL x16, żeby wycisnąć max z procka i teraz problem nie występuje. Tak samo nie występował na wyższych poziomach optymalizacji.

    W takim razie serdecznie dziękuję za pomoc. Sam rozważałem czy nie brakuje mi mocy, ale byłem najwyraźniej zmylony poprzez moje założenia, jednak świeże spojrzenie z boku pomaga lepiej pewne rzeczy dostrzec.

    I mam nadzieję, że pozostali Panowie mieli tylko słabszy dzień, bo obaj byli uosobieniem tego, jak wygląda teraz to forum we wszystkich anegdotach. Czepianie się drobnostek niezwiązanych z tematem, uwaga, że powinienem czymś innym się w życiu zająć, i lakoniczne, kpiące i niestety mało wnoszące do tematu odpowiedzi.

    Dodano po 1 [minuty]:

    Problemem okazały się brak mocy procesora, stąd wyższy poziom optymalizacji pomógł go, przynajmniej tymczasowo, rozwiązać. Dalej jestem ciekaw czemu tak się stało, mimo poszlak, że procesor tej wolnej mocy powinien mieć więcej, jednak zapewne bez wnikliwej analizy kodu nie uda się znaleźć odpowiedzi na to pytanie, a nikogo na to nie chcę narażać ;)
  • #18 19292340
    tmf
    VIP Zasłużony dla elektroda
    lupin22 napisał:
    Problemem okazały się brak mocy procesora, stąd wyższy poziom optymalizacji pomógł go, przynajmniej tymczasowo, rozwiązać. Dalej jestem ciekaw czemu tak się stało, mimo poszlak, że procesor tej wolnej mocy powinien mieć więce

    Nie podałeś jakim zegarem taktujesz procek (XMEGA domyślnie to 8 MHz, ale można max 32 MHz), nie podajesz jak kodowany jest dźwięk (PWM, ADPCM, uLaw, inne?), nie podajesz w jaki sposób generujesz dźwięk - DAC, PWM? Nie pokazujesz kodu. Oczekujesz pomocy. Chętnie ci pomożemy, ale musisz dać nam szansę. Bez tych podstawowych informacji można wróżyć z fusów, co widać w tym wątku.
    Obawiam się, że twoje tymczasowe rozwiązanie, może nie być poprawne.
  • #19 19292721
    lupin22
    Poziom 10  
    tmf napisał:
    lupin22 napisał:
    Problemem okazały się brak mocy procesora, stąd wyższy poziom optymalizacji pomógł go, przynajmniej tymczasowo, rozwiązać. Dalej jestem ciekaw czemu tak się stało, mimo poszlak, że procesor tej wolnej mocy powinien mieć więce

    Nie podałeś jakim zegarem taktujesz procek (XMEGA domyślnie to 8 MHz, ale można max 32 MHz),

    Zegar taktowany jest 24MHz. Wynika to z tego, że używam oscylatora 32MHz do komunikacji z USB, dlatego jest skonfigurowany na 48MHz, a następnie, żeby nie przekraczać tych maksymalnych 32MHz użyty jest preskaler 2.

    tmf napisał:

    nie podajesz jak kodowany jest dźwięk (PWM, ADPCM, uLaw, inne?), nie podajesz w jaki sposób generujesz dźwięk - DAC, PWM?

    Przepraszam, w oryginalnym poście było to napisane, ale nie chciałem za dużo informacji na raz. Próbki są w formacie unsigned 8bit, poprzez system DMA podawane na DAC procesora - korzystam tutaj ze zmodyfikowanego przykładu Pana książki.

    tmf napisał:

    Nie pokazujesz kodu. Oczekujesz pomocy. Chętnie ci pomożemy, ale musisz dać nam szansę. Bez tych podstawowych informacji można wróżyć z fusów, co widać w tym wątku.

    Kod jest dość obszerny. Niemniej jednak przybliżę, jak wygląda działanie programu i jeśli trzeba również poszczególne aspekty.

    Głównym zadaniem jest odtwarzanie dźwięku, który jest odtwarzany cały czas, gdy włączone jest urządzenie. Jest za to odpowiedzialny, jak wyżej wspomniałem system DMA podający próbki na DAC. Próbki ładowane są do dwuwymiarowej tablicy z zewnętrznej pamięci Flash poprzez SPI. Maksymalnie ładowane są cztery dźwięki osobne na raz, po czym są one składane w dźwięk wynikowy w odpowiednich proporcjach. Jest to częściowo pokazane w tym temacie Uproszczenie obliczeń proporcjonalności dwóch zmiennych. Ogólna zasada jest taka:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Suma współczynników jest równa 128 (po to, by dzielić przez 128 za pomocą przesunięcia bitowego) a offset to suma współczynników konieczna do przesunięcia dźwięku tak, by dalej linią "zero" była wartość 127. BUFFER_SIZE to 480. Dźwięk odtwarzany jest z częstotliwością 22kHz.

    Ta pętla jest głównym obciążeniem programu. Przeważnie jest w formie prostszej, tzn odtwarzane są 2-3 dźwięki jednocześnie, natomiast sytuacje gdy jest ich 4 również muszą być obsługiwane.

    Drugie przerwanie, wywoływane z częstotliwością 1kHz ma za zadanie liczyć milisekundy oraz wykonywać efekty świetlne podając na diody LED próbki czytane również z zewnętrznej pamięci Flash, na podobnej zasadzie co dźwięk, tylko ze znacznie mniejszą częstotliwością - są to 4 próbki 8bitowe z częstotliwością 100Hz podawane na 4 osobne kanały PWM.

    W tym zamyka się część w przerwaniach.

    W pętli głównej programu obsługiwane są przyciski, potencjalna komunikacja USB oraz czytane są dane z żyroskopu oraz akcelerometru poprzez TWI.

    tmf napisał:

    Obawiam się, że twoje tymczasowe rozwiązanie, może nie być poprawne.

    Również nie jestem fanem zwiększania optymalizacji, na szczęście w zanadrzu mam jeszcze drugie rozwiązanie, o którym wspomniałem, czyli zostawienie oscylatora 32MHz tylko do USB, a zegar systemowy ustawić na 32MHz poprzez użycie oscylatora 2MHz i PLL x16. Czyli 50% szybciej, niż wcześniej (24MHz). Oczywiście wolałbym tego nie robić, ale nie wiem jak sprawdzić, która część programu jest najbardziej problematyczna.

    Moje dotychczasowe wnioski są takie, że przerwania muszą wykorzystywać znakomitą część mocy procesora i w momencie, kiedy jej braknie, to dźwięk powoli dostaje opóźnienia, co objawia się efektem reverbu aż do rozjechania się całkowitego.

    Dziwi mnie tylko, że cokolwiek co robię w pętli głównej może mieć tak poważny wpływ na to, co dzieje się w przerwaniach. Zwłaszcza, że po zmianach wprowadzonych do wyliczania próbek dźwięku, które wcześniej wyglądało tak:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Także miałem tutaj twarde dzielenie przez 100 i mimo, że było mniej do policzenia, to chyba i tak teraz jest szybciej. Oczywiście trzeba wczytać 2x więcej próbek (w przypadku czterech dźwiękó na raz), niemniej jednak byłem przekonany, że rzeczywiście jest szybciej - wzory świetlne w drugim przerwaniu zaczęły wykonywać się szybciej, a czasy ustalone dla przycisków skróciły się (to samo przerwanie obsługuje ledy i mierzenie czasu) więc uznałem, że to wcześniej program był obciążony na tyle, że czas liczył się wolniej, natomiast teraz udało się wszystko tak zoptymalizować, że procesor spokojnie nadąża z obydwoma przerwaniami. Zmieniłem też taktowanie SPI z 3MHz na 12MHz ze względu na większą ilość próek.
  • #20 19292997
    JacekCz
    Poziom 42  
    tmf napisał:
    Nie pokazujesz kodu. Oczekujesz pomocy. Chętnie ci pomożemy, ale musisz dać nam szansę. Bez tych podstawowych informacji można wróżyć z fusów, co widać w tym wątku.
    Obawiam się, że twoje tymczasowe rozwiązanie, może nie być poprawne.


    Prawie na pewno nie jest, tylko pudruje na chwilę jakiś inny problem.

    Dodano po 4 [minuty]:

    lupin22 napisał:
    for (uint16_t i = 0; i < BUFFER_SIZE; i++)
    {
    (samplebuffer[channel] = coeff1 * samples1[i] + coeff2*samples2[i]+ coeff3*samples3[i] + coeff4*samples4[i])/128 + offset;
    }


    Karmisz nasz fragmentami, które zostały uszkodzone przy preparowaniu do publikacji.

    Moja szklana kula mówi, że problem zmiennej statycznej nie jest związany w wydajnością - różnice kodów maszynowych zmiennej statycznej a 'auto' (na stosie) to 1-2 takty, to jest (jak w wojsku 1/2 litra) NIC w stosunku do tysięcy taktów algorytmu.
    [/i][/i][/i][/i]
  • #21 19293067
    lupin22
    Poziom 10  
    JacekCz napisał:

    lupin22 napisał:
    for (uint16_t i = 0; i < BUFFER_SIZE; i++)
    {
    (samplebuffer[channel] = coeff1 * samples1[i] + coeff2*samples2[i]+ coeff3*samples3[i] + coeff4*samples4[i])/128 + offset;
    }


    Karmisz nasz fragmentami, które zostały uszkodzone przy preparowaniu do publikacji.


    Proszę bardzo:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Czy ten fragment jest bardziej czytelny?

    Cały kod ma kilka tysięcy linii i wolałbym go tutaj nie wstawiać, chętnie przybliżę jego fragmenty, ale chyba nikt nie chce analizować tutaj całego mojego programu.

    JacekCz napisał:

    Moja szklana kula mówi, że problem zmiennej statycznej nie jest związany w wydajnością - różnice kodów maszynowych zmiennej statycznej a 'auto' (na stosie) to 1-2 takty, to jest (jak w wojsku 1/2 litra) NIC w stosunku do tysięcy taktów algorytmu.[/i][/i][/i][/i][/i]

    Zgadzam się, stąd moje zdziwienie. Ale koniec końców nawet wersja ze staticiem dała daki sam efekt, tylko trochę później. Pomogła zmiana optymalizacji z O1 na O2. Pomogła też zmiana taktowania na wyższe, bez zmiany optymalizacji. W obu tych przypadkach nie ma znaczenia, czy jest static, czy nie. Samemu trudno mi uwierzyć, że tak duży wpływ jest takiej drobnostki, ale jak szukać dalej?
  • #22 19294132
    tmf
    VIP Zasłużony dla elektroda
    Mam wrażnie, że już kiedyś analizowaliśmy ten kod. Przypuszczam, że tu problemem, z któym nie radzi sobie optymalnie kompilator są indeksowania tablic. AVR nie ma za wielu rejestrów indeksowych, a te, które są mają różną funkcjonalność. Dlatego, mimo, że zwykle tak bym nie postępował, radziłbym pomóc nieco kompilatorowi, wyrzucając, szczególnie adresację wielowymiarowych tablic, gdzie jeden z indeksów jest stałą, jako wskaźnik na konkretny wymiar tablicy.
    Zapewne, warto też przypatrzeć się komunikacji z zewnęrzną pamięcią, tu zapewne też da się coś poprawić.
    lupin22 napisał:
    W obu tych przypadkach nie ma znaczenia, czy jest static, czy nie. Samemu trudno mi uwierzyć, że tak duży wpływ jest takiej drobnostki, ale jak szukać dalej?

    Rzuć okiem na takie newralgiczne miejsca jak powyżej, jak wyglądają przy O1 i O2, mogą być spore różnice.
    Ja zwykle robię kompilację na O0 jak debuguję, ew. O1, ale produkcyjnie praktycznie zawsze na Os.
  • #23 19294521
    lupin22
    Poziom 10  
    tmf napisał:
    Mam wrażnie, że już kiedyś analizowaliśmy ten kod.

    Tak było.
    tmf napisał:

    lupin22 napisał:
    W obu tych przypadkach nie ma znaczenia, czy jest static, czy nie. Samemu trudno mi uwierzyć, że tak duży wpływ jest takiej drobnostki, ale jak szukać dalej?

    Rzuć okiem na takie newralgiczne miejsca jak powyżej, jak wyglądają przy O1 i O2, mogą być spore różnice.
    Ja zwykle robię kompilację na O0 jak debuguję, ew. O1, ale produkcyjnie praktycznie zawsze na Os.


    Tutaj trzy pytania - czy są jakieś przeciwwskazania, żeby korzystać z O1,2,3? I skoro program działa na O0 (mój się nie mieści niestety), to po co dawać mu potem Os?

    A trzecie dotyczy już konkretnie mojego kodu. Nie ukrywam, assembler nie jest mi znany i chociaż wiem, czym jest to daleko mi do takiej biegłości, żeby go normalnie czytać. Udało mi się znaleźć w pliku .lss funkcję, która obrabia próbki na O1, ale przy ustawieniu O2 już nie jestem w stanie tego zrobić, bo nie jest normalnie rozpisana tak, jak była wcześniej. Chciałem sobie porównać, ale nie wiem co z czym.

    Tak wygląda to na O1:
    Kod: AVR assembler
    Zaloguj się, aby zobaczyć kod

    Nawet się ucieszyłem, jak zobaczyłem, że ładnie rozpisane są linie kodu i już nabrałem nadziei, że coś z tego wyciągnę, ale potem pokazało się moim oczom to, co wyżej, gdzie jest wykonywane teoretycznie 3 razy to samo na różne sposoby za każdym razem. Przy O2 jedyny odnośnik do processs_samples (sic) wygląda tak:
    Kod: AVR assembler
    Zaloguj się, aby zobaczyć kod

    I pod linią process samples jest kod C z innej funkcji.

    Jeśli chodzi o komunikację z SPI to robiłem to o ile dobrze pamiętam z datasheetem w ręku z takim rezultatem:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod


    I funkcja czytająca x bajtów:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod
  • #24 19294872
    tmf
    VIP Zasłużony dla elektroda
    lupin22 napisał:
    Tutaj trzy pytania - czy są jakieś przeciwwskazania, żeby korzystać z O1,2,3? I skoro program działa na O0 (mój się nie mieści niestety), to po co dawać mu potem Os?

    To są różne poziomy optymalizacji. Zasadniczo robisz optymalizację pod kątem objętości (Os) lub szybkości. O0 lub O1 stosuje się praktycznie tylok przy debugowaniu - właśnie ze względu na fakt, który zaobserwowałeś - następuje prosta translacja C na asembler i łatwo się w tym połapać, a przede wszystkim ze względu na brak optymalizacji, debuger nie "skacze" po programie. W finalnej aplikacji tych poziomów się nie stosuje. Nie, że jest to błędem, ale po prostu kod jest większy i wolniejszy. Przy wyższych poziomach zaczyna kompilator zaczyna optymalizować kod, w efekcie nie ma relacji 1:1 pomiędz kodem w C i asemblerem - debugowanie zaczyna być uciążliwe, przynajmniej takie debugowanie konkretnych linii kodu.
    Co do SPI - zamień transfery SP na transfery realizowane przez DMA i dodaj podwójne buforowanie. Do jednego bufora DMA ci będzie ładowało próbki, a na drugim będziesz robił obliczenia. Właśniciw przydałby się jeszcze kolejny - z którego przez DMA odtwarzasz próbki. W ten sposób zyskasz mnóstwo czasu, jaki tracisz na czekaniu w pętli na transfery SPI. W najszybszym trybie 1 transfer SPI to 16 taktów CPU, do tego dodaj zmarnowane takty na organizację pętli i odbiór danych. niech to będzie ze 30 taktów/bajt, co dla 512 bloku daje ci zmarnowanych 15 tys. taktów/blok.
  • #25 19295278
    lupin22
    Poziom 10  
    tmf napisał:

    Co do SPI - zamień transfery SP na transfery realizowane przez DMA i dodaj podwójne buforowanie. Do jednego bufora DMA ci będzie ładowało próbki, a na drugim będziesz robił obliczenia. Właśniciw przydałby się jeszcze kolejny - z którego przez DMA odtwarzasz próbki. W ten sposób zyskasz mnóstwo czasu, jaki tracisz na czekaniu w pętli na transfery SPI. W najszybszym trybie 1 transfer SPI to 16 taktów CPU, do tego dodaj zmarnowane takty na organizację pętli i odbiór danych. niech to będzie ze 30 taktów/bajt, co dla 512 bloku daje ci zmarnowanych 15 tys. taktów/blok.

    Trochę mnie to przerasta, zwłaszcza, że tutaj ładuję czasem jeden bufor, czasem 4, a z tej samej pamięci czytam dane jeszcze w innym przerwaniu.

    Zwróciłem jednak uwagę na co innego, bo być może optymalizując te przerwania próbujemy rozwiązać problem, którego nie ma. Otóż właśnie za pomocą SPI mogę dość łatwo przetestować sobie, co się dzieje w programie gdy te przerwania obrabiające próbki wykonują się zbyt długo. Efekt jest inny niż to, co działo się w sytuacji opisanej na początku tematu. Testuję działanie programu mrugając ledem co 10 sekund, mruganie jest zrealizowane w pętli głównej, funkcja millis jest w pierwszym poście, pobiera ona wartość elapsed_time z przerwania wywoływanego w zamierzeniu co 1ms:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod


    Wyniki działania (mierzone stoperem, więc nie super precyzyjne +-0.5s):

    Kod: C / C++
    Zaloguj się, aby zobaczyć kod


    Przy wszystkich tych opcjach, również najwolniejszej, dźwięk odtwarza się w zupełności poprawnie, nie ma żadnych niepokojących objawów. To by sugerowało, że jednak to nie brak mocy procesora jest problemem. W końcu jedyny związek między tym, co robi program w pętli głównej (czas jej wykonywania jest w zasadzie nieistotny, teraz jest 4-5ms, mogłoby być i 20ms i nic by się nie stało) a przerwaniami, które są krytyczne jest funkcja millis, która w ATOMIC_BLOCK przepisuje longa. No ale chyba wyłączenie przerwań na kilka taktów potrzebnych na przebisanie 4-bajtowej wartości raz na kilka ms nie może dawać takich efektów? Wyniki tego testu są takie, jakbym się spodziewał - wszystko działa poprawnie nawet przy znacznie większym obciążeniu, tylko funkcje czasowe wykonują się wolniej.

    Co więcej, problem ze staticiem i jego brakiem jest dalej widoczny - we wszystkich tych przypadkach, łącznie z 32MHz, czyli dorzuceniem 50% mocy obliczeniowej procesora.

    Podsumowując najnowsze doniesienia z "frontu":

    Przy optymalizacji O1, jeśli nie ma statica, dziwny problem z dźwiękiem pojawia się niezależnie od tego, czy SPI jest na 3MHz, 12MHz, czy 16MHz. Niezależnie od tego, czy procesor działa na 24MHz czy na 32MHz. Nawet na najwolniejszym SPI, by sztucznie zwiększyć obciążenie w przerwaniach powodujące to, że czas naliczany przez procesor jest 2 razy wolniej, niż powinien, zawsze jest tak samo. Zmiana optymalizacji na O2 usuwa problem w każdym z przypadków. Tak samo zrobienie static tego longa problem rozwiązuje, również przy optymalizacji O1.

    Ehh, zawsze coś się musi przytrafić, miło sobie człowiek programuje nowe funkcje a tutaj taka kosmiczna niespodzianka. Na Os zmienić nie mogę, wtedy program ma 22kB zamiast 24-25kB na O1/2, natomiast słychać pyrkanie i inne niepokojące objawy w dźwięku. Na O0 za to jest za duży, bo ma prawie 37kB.
  • Pomocny post
    #26 19295368
    tmf
    VIP Zasłużony dla elektroda
    lupin22 napisał:
    Zmiana optymalizacji na O2 usuwa problem w każdym z przypadków. Tak samo zrobienie static tego longa problem rozwiązuje, również przy optymalizacji O1.

    lupin22 napisał:
    na O1/2, natomiast słychać pyrkanie i inne niepokojące objawy w dźwięku.

    Z opisu wynika, że masz coś nie tak w programie, niestety bez wnikliwego przejrzenia całości nie ma szans, aby ci pomóc. W pewnych sytuacjach różnice w szybkości wykonywania programu pomiędzy -O0, a -O2 mogą przekraczać 50%.
    Jeśli tak napisany kod migający LEDem w pętli głównej, ma różnice w czasach wykonania 11-20s, to masz powaźnie skaszaniony kod. Zakładając, że millis() zwraca wartość ms inkrementowaną w przerwaniu, to nie ma szans, aby niezależnie od reszty kodu, LED migał z okresem istotnie innymi niż 10s. Okres się może zmienić wyłącznie jeśli w reszcie pętli głównej masz niezwykle długie funkcje, wykonujące się po kilka sekund, albo masz takie funkcje w kodzie przerwania . W obu przypadkach masz poważny błąd w kodzie.
    Ponadto, jeśli dźwięk jest odtwarzany z bufora, za pomocą DMA, to jedyna możliwość zniekształceń wynika z faktu nienadążania z uzupełnianiem danych do bufora. To można łatwo sprawdzić blokując uaktualnianie buforów (czyli odtwarzając stale tą samą próbkę) i sprawdzając czy też są zniekształcenia. Obstawiam, że jeśli intensywnie używasz przerwań to masz jakiś race condition i to prowadzi do problemów. Tłumaczyłoby to niemalże losowe pojawianie się problemu - bo naprawdę trudno to przypisać static, lub drobnym zmianom w poziomie optymalizacji kodu. Piszesz też, że do pamięci dataFLASH odwołujesz się z różnych miejsc, przez SPI. Jesteś pewien, że wszelkie transakcje na pamięci są atomowe? Dostęp do SPI np. z przerwania i innego przerwania lub pętli głównej wywołuje właśnie race condition. Jeśli czytanie dużych bloków danych jest przerywane czytaniem jakiś pojedynczych bajtów, to objawiałoby się to właśnie pyrkaniem lub dziwnym zniekształceniem dźwięku.
    Samo odtwarzanie 4 próbek 22 kHz to zaledwie 88 kB/s transferu, nawet te obliczenia jakie pokazałeś nie powinny zabić procka. To ciągle ponad 1000 taktów na każdą próbkę. Aż nadto. Nawet do tego dodając blokującą obsługę SPI, to w najgorszym razie jakieś 1,5-3 mln taktów/s (zakładając, że masz SPIClk=Clk/2), Ciągle kupa czasu pozostaje do dyspozycji.
  • #27 19295571
    lupin22
    Poziom 10  
    tmf napisał:

    Z opisu wynika, że masz coś nie tak w programie, niestety bez wnikliwego przejrzenia całości nie ma szans, aby ci pomóc. W pewnych sytuacjach różnice w szybkości wykonywania programu pomiędzy -O0, a -O2 mogą przekraczać 50%.
    Jeśli tak napisany kod migający LEDem w pętli głównej, ma różnice w czasach wykonania 11-20s, to masz powaźnie skaszaniony kod. Zakładając, że millis() zwraca wartość ms inkrementowaną w przerwaniu, to nie ma szans, aby niezależnie od reszty kodu, LED migał z okresem istotnie innymi niż 10s. Okres się może zmienić wyłącznie jeśli w reszcie pętli głównej masz niezwykle długie funkcje, wykonujące się po kilka sekund, albo masz takie funkcje w kodzie przerwania .

    Nie ma takich funkcji. Kod w pętli głównej wykonuje się około 7ms, jest to sprawdzane debuggerem. No, sprawdzam co prawda korzystając z moich millisów, ale chodzi tutaj o sam rząd wielkości. Mogę tam wstawić _delay_ms(2000) i nic złego się nie dzieje - oczywiście polling na przycisku to w takim wypadku żart, ale nie ma wpływu na resztę programu, sprawdzałem. W programie są dwa przerwania - jedno wczytuje te próbki i obrabia je, po czym bufor podawany jest na DMA. Drugie, wywoływane co 1ms inkrementuje zmienną czasu, a co 10 wywołań odczytuje kilka bajtów i podaje je na PWM. Ostatnio w ramach przepisywania części kodu nawet wywaliłem stamtąd operacje typu modulo, jak zobaczyłem jak długo się wykonują. Jest tam też kilka innych obliczeń, ale nic poważnego, żadnej pętli dłuższej niż kilka wywołań. W ramach testu wykomentowałem cały znajdujący się tam kod, teraz to przerwanie wygląda tak:

    Kod: C / C++
    Zaloguj się, aby zobaczyć kod


    Wykomentowałem też cały kod w pętli głównej. Teraz wygląda ona tak:

    Kod: C / C++
    Zaloguj się, aby zobaczyć kod


    Efekt identyczny. W tym wypadku program tylko zajmuje się dźwiękiem i liczy czas, nic więcej nie robi.

    tmf napisał:

    Piszesz też, że do pamięci dataFLASH odwołujesz się z różnych miejsc, przez SPI. Jesteś pewien, że wszelkie transakcje na pamięci są atomowe? Dostęp do SPI np. z przerwania i innego przerwania lub pętli głównej wywołuje właśnie race condition. Jeśli czytanie dużych bloków danych jest przerywane czytaniem jakiś pojedynczych bajtów, to objawiałoby się to właśnie pyrkaniem lub dziwnym zniekształceniem dźwięku.

    Tak, czytanie z dataFLASH było tylko w tych dwóch przerwaniach, nigdzie indziej. Zakładam więc, że tam jest to bezpieczne, bo jedno nie może przerwać drugiemu. Teraz pozostało już tylko i wyłącznie przerwanie odtwarzające dźwięk.


    Trzeba w takim razie zacząć sprawdzać przerwanie obrabiające próbki. Wyłączam kawałki kodu jedne po drugich i szukam przyczyny. I coś znalazłem, po naprawdę dziwnych rezultatach po drodze. Trudno mi w to uwierzyć, ale czyżbym popełnił niewybaczalny błąd?

    Kod: C / C++
    Zaloguj się, aby zobaczyć kod


    nie było zainicjalizowane niczym.

    Po zmianie na
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    problem wydaje się, że zniknął.

    Czy doprowadziłem tutaj do jakiegoś UB? I przez przypadek go jakoś wywołałem w zupełnie innym miejscu kodu? Ale mi wstyd :oops:
  • Pomocny post
    #28 19295617
    Konto nie istnieje
    Poziom 1  
  • #29 19295624
    lupin22
    Poziom 10  
    0xdeadbee napisał:

    O ile rozumiem ta zmienna to zmienna lokalna zadeklarowana w funkcji.

    Tak.

    0xdeadbee napisał:
    Użycie niezainicjalizowanego obiektu mającego "automatic storage duration" jest UB

    Czyli jednak. Taka prosta rzecz, a tyle problemów. 90% czasu spędzam teraz z C#, który w sytuacji niezainicjalizowanej zmiennej wali error i wiadomo, że jest problem.

    Dodano po 42 [sekundy]:

    0xdeadbee napisał:
    Ja osobiście jednak polecałbym -Wall -Wextra -pedantic i czytać warningi :)


    Chyba tak trzeba zrobić, tyle czasu na to zmarnowałem, że mam nauczkę.
  • #30 19308867
    lupin22
    Poziom 10  
    tmf napisał:
    Zakładając, że millis() zwraca wartość ms inkrementowaną w przerwaniu, to nie ma szans, aby niezależnie od reszty kodu, LED migał z okresem istotnie innymi niż 10s.


    Pozwolę sobie skorzystać z okazji jeszcze i dopytać o tę kwestię. Bo ewidentnie u mnie się tak dzieje i zależy to od tego, ile się dzieje w tym drugim przerwaniu, odtwarzającym próbki. Przy 22kHz i buforze 480 próbek przerwanie obrabiające próbki wykonuje się średnio co 22ms. Jeśli jest puste, liczony czas jest poprawny - wychodzi około 9.8s na stoperze, to chyba w granicach błędu, zwłaszcza, że mierzę "ręcznie".

    Z tego wnioskuję, że w trakcie przerwania obsługującego próbki przerwanie liczące czas wywoływane co 1ms może "wywołać się" kilka razy - tzn. ustawi się flaga, ale jako, że flaga jest zerojedynkowa to program wykona to przerwanie tylko raz, nawet jeśli w międzyczasie trigger na liczniku wywołał się kilka razy.

    Żeby to sprawdzić zrobiłem eksperyment - przerwanie wywoływałem co 1ms, 2ms oraz co 5ms. Wyniki były następujące - dla 1ms (standard) = 11.41s, dla 2ms = 10.7s, dla 5ms = 9.8s. To wydawałoby się potwierdzać moją teorię, że część przerwań jest gubiona, a po zmniejszeniu ich częstotliwości na tyle, że na pewno nie wywołają się 2 lub więcej razy w ciągu jednego przerwania liczącego próbki problem znika.

    Przychodzą mi na myśl dwa rozwiązania - albo liczyć co 5ms lub nawet więcej (w wykonywanym programie potrzebuję rozdzielczości 10ms, także mogę sobie pozwolić nawet na to), albo skrócić przerwanie liczące próbki, co wydaje się zadaniem znacznie trudniejszym, jeśli w ogóle możliwym w tak znaczącym stopniu, żeby problem zlikwidować.

    Czy moje rozumowanie jest poprawne?

Podsumowanie tematu

Użytkownik napotkał problem w programie embedded na procesorze XMEGA, który wykrzacza się po kilkunastu sekundach działania, co objawia się zniekształceniem dźwięku. Problem wydaje się związany z używaniem zmiennej lokalnej `time` w pętli głównej, która nie jest zadeklarowana jako `static`. W dyskusji poruszono kwestie związane z nieprzewidywalnym zachowaniem zmiennych lokalnych w C, które mogą prowadzić do undefined behavior (UB) oraz wpływu optymalizacji kompilatora na wydajność programu. Użytkownik zauważył, że zmiana poziomu optymalizacji z O1 na O2 poprawiła sytuację, co sugeruje, że problem może być związany z wydajnością przetwarzania w przerwaniach. Wskazano również na możliwość gubienia przerwań, co może wpływać na dokładność pomiaru czasu. Użytkownik rozważał również użycie DMA do poprawy wydajności transferu danych.
Podsumowanie wygenerowane przez model językowy.
REKLAMA