Używam odmiany 5-krzyżowego filtra środkowego na danych obrazu w małym systemie osadzonym, tj
x
x x x
x
Algorytm jest naprawdę prosty: odczytaj 5 liczb całkowitych bez znaku, uzyskaj najwyższe 2, wykonaj kilka obliczeń i zapisz wynik na liczbach całkowitych bez znaku.
Ładne jest to, że 5 wartości całkowitych wejściowych mieści się w zakresie 0-20. Obliczona wartość całkowita również mieści się w zakresie 0-20!
Dzięki profilowaniu zorientowałem się, że uzyskanie dwóch największych liczb jest wąskim gardłem, więc chcę przyspieszyć tę część. Jaki jest najszybszy sposób na dokonanie tego wyboru?
Obecny algorytm wykorzystuje maskę 32-bitową z 1 w pozycji podanej przez 5 liczb i obsługiwaną przez HW funkcję CLZ.
Powinienem powiedzieć, że procesor jest zastrzeżony, niedostępny poza moją firmą. Mój kompilator to GCC, ale dostosowany do tego procesora.
Próbowałem dowiedzieć się, czy mogę użyć tabeli odnośników, ale nie udało mi się wygenerować klucza, którego mogę użyć.
Mam kombinacji dla danych wejściowych, ale kolejność nie jest ważna, tzn. Jest taka sama jak .[5,0,0,0,5]
[5,5,0,0,0]
Zdarza się, że funkcja skrótu poniżej tworzy idealny skrót bez kolizji!
def hash(x):
h = 0
for i in x:
h = 33*h+i
return h
Ale skrót jest ogromny i po prostu nie ma wystarczającej ilości pamięci, aby z niego skorzystać.
Czy istnieje lepszy algorytm, którego mogę użyć? Czy jest możliwe rozwiązanie mojego problemu za pomocą tabeli przeglądowej i wygenerowania klucza?
źródło
hash
już wykonuje więcej operacji. Czy kolejne wywołania metody są powiązane, np. Czy centralax
przechodzi przez matrycę rząd po rzędzie?Odpowiedzi:
W mojej drugiej odpowiedzi sugeruję, że skoki warunkowe mogą być główną przeszkodą dla wydajności. W rezultacie przychodzą na myśl sieci sortujące : są one niezależne od danych, to znaczy ta sama sekwencja porównań jest wykonywana bez względu na dane wejściowe, przy czym tylko wymiany są warunkowe.
Oczywiście sortowanie może być zbyt pracochłonne; potrzebujemy tylko dwóch największych liczb. Na szczęście dla nas przebadano również sieci selekcyjne . Knuth mówi nam, że można znaleźć dwie najmniejsze liczby z pięciu²U^2)( 5 ) = 6 porównania [1, 5.3.4 ex 19] (i co najwyżej tyle swapów).
Sieć, którą podaje w rozwiązaniach (przepisana na tablice zerowe) to
który implementuje - po dostosowaniu kierunku porównań - w pseudokodzie jako
Teraz naiwne implementacje nadal mają skoki warunkowe (w całym kodzie wymiany). Jednak w zależności od maszyny można je wymyślić za pomocą instrukcji warunkowych. x86 wydaje się być zwykłym błotnistym ja; ARM wygląda bardziej obiecująco, ponieważ najwyraźniej większość operacji jest uwarunkowana sama w sobie. Jeśli dobrze rozumiem instrukcje , pierwsza zamiana przekłada się na to, zakładając, że nasze wartości tablic zostały załadowane do rejestrów
R0
poprzezR4
:Tak, tak, oczywiście możesz używać zamiany XOR z EOR .
Mam tylko nadzieję, że twój procesor ma to lub coś podobnego. Oczywiście, jeśli zbudujesz coś w tym celu, może uda ci się tam podłączyć sieć?
Jest to prawdopodobnie (możliwe?) Najlepsze, co możesz zrobić w klasycznej dziedzinie, tj. Bez korzystania z ograniczonej domeny i wykonywania nikczemnych magii wewnątrz słowa.
źródło
Oto algorytm bezpośredni, który znajduje się na stole:
Dzięki sprytnej implementacji
if ... else
można pozbyć się pewnych bezwarunkowych skoków, jakie miałoby bezpośrednie tłumaczenie.To jest brzydkie, ale zajmuje tylko
W rzeczywistości sześć porównań jest optymalnych dla tego problemu, jak pokazuje Twierdzenie S w sekcji 5.3.3 [1]; tutaj potrzebujemyW.2)( 5 ) .
Jednak nie można oczekiwać, że będzie to szybkie na maszynach z potokowaniem; biorąc pod uwagę wysoki odsetek skoków warunkowych, większość czasu prawdopodobnie spędziłaby w przeciągnięciu.
Zauważ, że prostszy wariant - rodzaj
x1
ix2
, a następnie włóż innymi wartościami następnie - trwa od czterech do siedmiu porównań i tylko pięć do sześciu zadań. Ponieważ spodziewam się, że skoki będą tu droższe, utknąłem przy tym.źródło
Może to być świetna aplikacja i test dla projektu Souper . Souper to superoptimizer - narzędzie, które pobiera krótką sekwencję kodu jako dane wejściowe i próbuje zoptymalizować go w jak największym stopniu (próbuje znaleźć równoważną sekwencję kodu, która będzie szybsza).
Souper jest open source. Możesz spróbować uruchomić Souper na swoim fragmencie kodu, aby sprawdzić, czy da to coś lepszego.
Zobacz także konkurs Johna Regehra na pisanie szybkiego kodu do sortowania 16 4-bitowych wartości ; możliwe, że niektóre techniki mogą być przydatne.
źródło
Możesz użyć213) tabela, która pobiera trzy liczby całkowite i wyświetla dwie największe. Następnie możesz użyć trzech odnośników tabeli:
T[T[T[441*a+21*b+c]*21+d]*21+e]
Podobnie za pomocą214 tabeli, możesz zredukować ją do dwóch wyszukiwań tabeli, choć nie jest jasne, że byłoby to szybsze.
Jeśli naprawdę chcesz mały stolik, możesz użyć dwóch212) tabele, aby „posortować” dwie liczby, a następnie użyć sieci sortującej. Według Wikipedii wymaga to maksymalnie 18 przeszukiwania tabel (9 komparatorów); możesz być w stanie zrobić mniej, ponieważ (1) chcesz znać tylko dwa największe elementy, i (2) dla niektórych bramek porównawczych, możesz być zainteresowany tylko maksimum.
Możesz także użyć jednego212) stół. Wdrożenie sieci sortującej zużywa mniej pamięci, ale więcej arytmetyki. W ten sposób uzyskuje się maksymalnie 9 odnośników do tabeli.
źródło