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

Uproszczenie obliczeń proporcjonalności dwóch zmiennych

lupin22 21 Sie 2020 11:13 762 15
  • #1 18881409
    lupin22
    Poziom 10  
    Cześć,

    w moim programie na procesor XMega32A4U-AU taktowanym częstotliwością 24MHz odtwarzam dźwięk (korzystam z kodu źródłowego tmf) .

    Dźwięk jest w formacie 8-bitowym bez znaku. Odtwarzany z częstotliwością 22KHz, z bufora podwójnego za pomocą systemu DMA. Wielkość bufora to 2x480b.

    Próbki odczytywane są z pamięci zewnętrznej FLASH poprzez interfejs SPI.

    W przerwaniu DMA odczytywane są dwa bufory:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Bufor główny podwójny - mainbuffer oraz pomocniczy new buffer.

    Następnie muszę dokonać operacji skalowania każdego z dźwięków. Wygląda to tak:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Wartość 127 jest wartością "neutralną" czyli wtedy membrana głośnika jest w spoczynku. Wartość volume_coefficient zawiera się w przedziale 0-100, także w efekcie mamy tutaj główne próbki odtwarzane z połowiczną głośnością oraz do tego dodawane nowe próbki z głośnością w zakresie 0-50% tak, żeby nie przekroczyć sumarycznie maksimum.

    Problem polega na tym, że chciałbym sterować również stosunkiem próbek głównych do próbek nowych tak, żeby nowe próbki były odtwarzane z głośnością 0-80%, a próbki główne w zakresie 50%-20% (im głośniejsze próbki nowe tym cichsze próbki główne tak, żeby sumarycznie po ich dodaniu wychodziło wciąż 100% głośności.

    Do zrealizowania tego próbowałem zastosować ten sam algorytm, co przy nowych próbkach:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    ale niestety taka ilość obliczeń pokonała już mój mikroprocesor. Czy wyrażenie:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    da się zapisać albo zastąpić czymś, co w mniejszym stopniu obciąży procesor?

    Zauważyłem, że program działa w wersji oryginalnej (główny dźwięk ciągle 50%, a nowy 0-50%) przy zapisie:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    ale jeśli próbuję ustawić proporcje na twardo, np.:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    To już przeciążam procesor.

    Stąd wnioskuję, że dodawanie dwóch wartości i dzielenie ich na 2 jest ładnie optymalizowane, ale w innych proporcjach już tak nie jest. Dlatego zastanawiam się, czy i tego wyrażenia wspomnianego wyżej nie dałoby się uprościć tak, żeby kilkukrotnie przyspieszyć jego wykonywanie. Może da się to jakoś sprytnie zrealizować?

    Z góry serdeczne dzięki!
  • #3 18881450
    khoam
    Poziom 42  
    lupin22 napisał:
    da się zapisać albo zastąpić czymś, co w mniejszym stopniu obciąży procesor?

    lupin22 napisał:
    czy i tego wyrażenia wspomnianego wyżej nie dałoby się uprościć tak, żeby kilkukrotnie przyspieszyć jego wykonywanie.

    Można użyć operacji stałoprzecinkowych zamiast operacji na float. Kod i dokumentacja w załączeniu.
  • Pomocny post
    #4 18881496
    tmf
    VIP Zasłużony dla elektroda
    khoam napisał:
    Można użyć operacji stałoprzecinkowych zamiast operacji na float.

    Autor nie ma nigdzie operacji float.
    lupin22 napisał:
    (100 - volume_coefficient) *127)/100

    Ta część obliczeń jest stałą w pętli, więc nie powinna wprowadzać żadnych obciążeń.
    W wielu miejscach rzutujesz typ na 32-bity, podczas, gdy wynik operacji nie może przekroczyć 16 bitów. Takie rzutowanie wprowadza więc niepotrzebne obliczenia. Zastanów się, czy ten wspólczynik nie może być warością z zakresu 0-128 - wtedy dzielenie byłoby prostą operacją przesunbięcia bitów, zamiast dzielenia. W jednym miejscu możesz mieć max 24 bity - użyj więc typu 24-bitowego.
    lupin22 napisał:
    Stąd wnioskuję, że dodawanie dwóch wartości i dzielenie ich na 2 jest ładnie optymalizowane, ale w innych proporcjach już tak nie jest.


    Dokładnie tak jest - dzielenie przez 2 to operacja przesunięcia bitowego, dzielenie przez 3 to już normalne wywołanie uniwersalnej funkcji dzielenia.
  • #5 18881567
    lupin22
    Poziom 10  
    bart-projects napisał:
    Przetestuj Mnoznik x= 0-255 a potem przesunięcie o osiem bitów.
    Mnoznik 255 powinien dać factor=1


    Chodzi o pomnożenie próbki przez liczbę x z zakresu 0-255 i potem przesunięcie o 8 bitów w prawo, żeby uzyskać próbkę o współczynniku x/255, tak?

    tmf napisał:

    lupin22 napisał:
    (100 - volume_coefficient) *127)/100

    Ta część obliczeń jest stałą w pętli, więc nie powinna wprowadzać żadnych obciążeń.


    Stałą? Przecież volume_coefficient to zmienna.

    tmf napisał:

    Zastanów się, czy ten wspólczynik nie może być warością z zakresu 0-128 -


    Jak najbardziej może tak być, nawet oszczędzi mi to jednej operacji mapowania wartości.

    tmf napisał:

    wtedy dzielenie byłoby prostą operacją przesunięcia bitów, zamiast dzielenia.


    Czy mógłbym prosić o jakąś dokładniejszą wskazówkę? Niestety wykorzystanie operacji bitowych do arytmetyki jeszcze nie przychodzi mi naturalnie. Chyba, że chodzi tutaj o rozwiązanie zasugerowane przez bart-projects tylko zamiast stosowania zakresu 0-255 zastosować 0-128 i przesuwać o 7 bitów w prawo? Czy dobrze rozumiem, że w ten sposób zostawiam operacje mnożenia jak są, ale oszczędzę na operacjach dzielenia, które przyjmą postać przesunięć bitowych?
  • Pomocny post
    #6 18881578
    tmf
    VIP Zasłużony dla elektroda
    lupin22 napisał:
    Chodzi o pomnożenie próbki przez liczbę x z zakresu 0-255 i potem przesunięcie o 8 bitów w prawo, żeby uzyskać próbkę o współczynniku x/255, tak?

    Dokładnie. Mnożenie ma niewielki narzut (procesor ma instrukcję MUL), a dzielenie przez 256 to po prostu obcięcie najmniej znaczącego bajtu.
    lupin22 napisał:
    Stałą? Przecież volume_coefficient to zmienna.

    Owszem, ale nie zmienia się w pętli, więc teoretycznie powinno to być obliczone raz i w pętli kompilator użyje wyliczonej stałej. Sprawdź w pliku lss czy tak robi, jeśli nie to ręcznie wyrzuć te obliczenia poza pętlę.
    lupin22 napisał:
    Czy mógłbym prosić o jakąś dokładniejszą wskazówkę? Niestety wykorzystanie operacji bitowych do arytmetyki jeszcze nie przychodzi mi naturalnie. Chyba, że chodzi tutaj o rozwiązanie zasugerowane przez bart-projects tylko zamiast stosowania zakresu 0-255 zastosować 0-128 i przesuwać o 7 bitów w prawo? Czy dobrze rozumiem, że w ten sposób zostawiam operacje mnożenia jak są, ale oszczędzę na operacjach dzielenia, które przyjmą postać przesunięć bitowych?

    Dokładnie tak. Kompilator sam to powinien zoptymalizować, jeśli dasz mu szansę. Więc sam w przesunięcia bitowe się nie musisz bawić. Pokaż jak wygląda całe przeliczenie, może da się jeszcze coś wymyślić.
  • Pomocny post
    #7 18881766
    Konto nie istnieje
    Poziom 1  
  • #8 18881896
    khoam
    Poziom 42  
    atom1477 napisał:
    Czyli w tym przypadku zwiększyć dzielnik ze 100 na 128, i tle samo razy zwiększyć mnożnik, czyli ze 127 na 163:

    Nie, ponieważ dzielnik o wartości 100 odnosi się do całej sumy:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod
    a nie tylko jej drugiego składnika.
  • #9 18881899
    Konto nie istnieje
    Poziom 1  
  • #10 18881935
    lupin22
    Poziom 10  
    tmf napisał:
    Pokaż jak wygląda całe przeliczenie, może da się jeszcze coś wymyślić.


    Teraz wygląda tak (wersja robocza):
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod


    Nie znam assemblera niestety, więc sprawdzanie kodu wynikowego na razie zostawiłem na później, także stałą wyciągnąłem przed pętlę na wszelki wypadek. Na razie zastosowałem stały stosunek nowych próbek do próbek oryginalnych (ale zmianę z dynamicznym stosunkiem już wstępnie testowałem i też jest okej) i wszystko śmiga pięknie. Pozbyłem się rzutowań (straszny wstyd, ale często wychodzą mi problemy przy przeliczaniu wartości, bo brakuje rzutowań, więc czasami dodaję je bez większego pomyślunku).

    Wartość volume_coefficient jest w zakresie 0-127 (nie do końca pamiętam dlaczego, bo oryginalny kod był pisany kilka lat temu, więc na razie się nie zagłębiam)

    atom1477 napisał:
    Trzeba tak dobierać mnożniki żeby dzielniki były potęgą dwójki.
    Czyli w tym przypadku zwiększyć dzielnik ze 100 na 128, i tle samo razy zwiększyć mnożnik, czyli ze 127 na 163:


    uint32_t newsample = (volume_coefficient *newbuffer[i] + (100 - volume_coefficient) *163)/128;

    Dokładnie to powinno być 162.56, ale ta niewielka niedokładność nie ma znaczenia w przypadku ustawiania głośności dźwięku.
    W drugim przypadku jest trudniej, i niedokładność będzie większa.

    Tutaj chyba nie do końca - wartość 127 w środku nawiasu odpowiada próbce o wartości 127 i dzięki temu uśredniany jest dźwięk. Także zmienić trzeba 100 na 127, żeby nie było problemu z underflowem. Pokazany wyżej kod działa już poprawnie.

    Szczerze mówiąc nie spodziewałem się, że takie obliczenia (mimo, że na stosunkowo dużych liczbach i wykonywane 480 razy pod rząd) będą aż tak dużym obciążeniem dla procesora. Przy taktowaniu 20KHz i buforze 480 próbek jego przeładowanie wychodzi raz na 20ms, co wydaje się ogromną ilością czasu... a jednak nie. Oczywiście procesor robi też jednocześnie kilka innych rzeczy i to też na pewno ma wpływ (być może znaczny).

    Teraz już łapię jak wykorzystywać przesunięcia bitowe, jak się okazuje nie taki diabeł straszny, jak go malują.

    Ten mój kod też jest marny na razie, ale chodziło o kwestię ilości obliczeń. Teraz uzbrojony w nową wiedzą mogę się zająć lepszym sposobem na miksowanie dźwięków.
  • #11 18881953
    Konto nie istnieje
    Poziom 1  
  • #12 18882033
    lupin22
    Poziom 10  
    atom1477 napisał:
    No i generalnie do obróbki dźwięku lepiej używać zmiennych signed.

    W mikrokontrolerze i tak musiałbym to potem zmieniać na unsigned.

    atom1477 napisał:
    aż nie bardzo rozumiem co on konkretnie liczy.

    Zmienna newsample powoduje przeskalowanie próbek z pomocniczego bufora smoothbuffer tak, żeby głośność była proporcjonalna do wartości volume_coefficient. Jako, że są to próbki w formacie unsigned nie mogę ich po prostu pomnożyć, bo linią bazową nie jest 0 (jak w próbkach signed), tylko połowa zakresu 0-255 czyli 127.

    A następnie te nowe próbki są odtwarzane razem z oryginalnymi w stosunku 2:1.
  • #13 18882101
    Konto nie istnieje
    Poziom 1  
  • #14 18882150
    lupin22
    Poziom 10  
    atom1477 napisał:
    Czyli chodzi o to żeby głośność była po prostu regulowana w zakresie 33...100%?

    Docelowo ma to działać tak:

    Dźwięk podstawowy leci sobie na głośności 50% i jest to maksymalna głośność tej składowej całości.
    Dźwięk dodatkowy jest sterowany zewnętrznie i będzie w zakresie 0-80% głośności.

    Oba dźwięki są dodawane.

    W momencie, kiedy dźwięk dodatkowy osiągnie poziom 50% konieczne będzie stopniowe zmniejszanie głośności dźwięku podstawowego, żeby sumarycznie nie przekroczyć tych 100%. Czyli w przypadku maksimum dla dźwięku dodatkowego (80%) dźwięk podstawowy musi być na poziomie 20%.

    Ogólnie to już raczej prosta kwestia i zostanie wyciągnięta poza przerwania i w funkcji, która zarządza głośnością dźwięku dodatkowego zrobię sobie dwa współczynniki - jeden dla dźwięku podstawowego, drugi dla dodatkowego i w tej pętli podmieniającej próbki zostanie tylko mniej więcej to:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod
  • Pomocny post
    #15 18882175
    khoam
    Poziom 42  
    Wyrażenie:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod
    to praktycznie to samo, co poniższe - uwzględniając zaokrąglenia w dół na operacjach liczb całkowitych:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod
    za wyjątkiem sytuacji, kiedy volume_coefficient będzie równy 127, ale przed tym się można zabezpieczyć:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod
    Będzie jeszcze szybciej ;)
  • #16 18882209
    lupin22
    Poziom 10  
    khoam napisał:
    Będzie jeszcze szybciej


    A dziękuję bardzo, rzeczywiście to prawda. Ale stała i tak już nie jest potrzebna, bo przy zmianie podejścia na dwa współczynniki obliczane wcześniej i przerzucenie całego związanego z tym kodu poza obsługę przerwania DMA prawdopodobnie zadziałałoby to nawet w starej wersji z liczbami nieokrągłymi.

    Tak czy siak dziękuję bardzo za pomoc, szkoda, że w szkole nie wbijają do głowy takich podstawowych zasad, jak właśnie to, że w mikrokontrolerze okrągłymi liczbami są potęgi dwójki.

    Problem rozwiązany a nabyta wiedza pozwoli usprawnić kilka innych miejsc w kodzie, gdzie też niepotrzebnie są skomplikowane obliczenia.

Podsumowanie tematu

W dyskusji poruszono problem skalowania głośności dźwięku w programie działającym na procesorze XMega32A4U-AU. Użytkownik odtwarza dźwięk w formacie 8-bitowym z częstotliwością 22 kHz, korzystając z podwójnego bufora i interfejsu SPI do odczytu próbek z pamięci FLASH. Uczestnicy sugerują optymalizację obliczeń poprzez zastosowanie operacji stałoprzecinkowych zamiast zmiennoprzecinkowych oraz wykorzystanie przesunięć bitowych do uproszczenia operacji dzielenia. Wskazano na konieczność przekształcenia wartości zmiennych oraz na znaczenie używania potęg dwójki w obliczeniach, co pozwala na zwiększenie wydajności. Użytkownik planuje wprowadzenie dynamicznego zarządzania głośnością dwóch dźwięków, aby nie przekroczyć 100% sumarycznej głośności.
Podsumowanie wygenerowane przez model językowy.
REKLAMA