Czy istnieje szybszy sposób niż x >= start && x <= end
w C lub C ++ sprawdzenie, czy liczba całkowita znajduje się między dwiema liczbami całkowitymi?
AKTUALIZACJA : Moja konkretna platforma to iOS. Jest to część funkcji rozmycia ramki, która ogranicza piksele do okręgu w danym kwadracie.
AKTUALIZACJA : Po wypróbowaniu zaakceptowanej odpowiedzi otrzymałem przyspieszenie o rząd wielkości w jednym wierszu kodu, zamiast robić to normalnie x >= start && x <= end
.
AKTUALIZACJA : Oto kod po i przed asemblerem z XCode:
NOWY SPOSÓB
// diff = (end - start) + 1
#define POINT_IN_RANGE_AND_INCREMENT(p, range) ((p++ - range.start) < range.diff)
Ltmp1313:
ldr r0, [sp, #176] @ 4-byte Reload
ldr r1, [sp, #164] @ 4-byte Reload
ldr r0, [r0]
ldr r1, [r1]
sub.w r0, r9, r0
cmp r0, r1
blo LBB44_30
STARA DROGA
#define POINT_IN_RANGE_AND_INCREMENT(p, range) (p <= range.end && p++ >= range.start)
Ltmp1301:
ldr r1, [sp, #172] @ 4-byte Reload
ldr r1, [r1]
cmp r0, r1
bls LBB44_32
mov r6, r0
b LBB44_33
LBB44_32:
ldr r1, [sp, #188] @ 4-byte Reload
adds r6, r0, #1
Ltmp1302:
ldr r1, [r1]
cmp r0, r1
bhs LBB44_36
Całkiem niesamowite, jak redukcja lub eliminacja rozgałęzień może zapewnić tak gwałtowne przyspieszenie.
c++
c
performance
math
jjxtra
źródło
źródło
Odpowiedzi:
Jest stara sztuczka, aby to zrobić za pomocą tylko jednego porównania / gałęzi. To, czy rzeczywiście poprawi prędkość, może być kwestią otwartą, a nawet jeśli tak jest, prawdopodobnie jest to zbyt mało, aby zauważyć lub się tym przejmować, ale kiedy zaczynasz od dwóch porównań, szanse na ogromną poprawę są dość niewielkie. Kod wygląda następująco:
W przypadku typowego, nowoczesnego komputera (tj. Cokolwiek używającego uzupełnienia dwójkowego) konwersja do niepodpisanego jest naprawdę nop - po prostu zmiana w sposobie wyświetlania tych samych bitów.
Zwróć uwagę, że w typowym przypadku można wstępnie wykonać obliczenia
upper-lower
poza (przypuszczalnie) zapętloną, więc zwykle nie zajmuje to znaczącego czasu. Wraz ze zmniejszeniem liczby instrukcji rozgałęzienia, to także (ogólnie) poprawia przewidywanie rozgałęzień. W takim przypadku brana jest ta sama gałąź, niezależnie od tego, czy liczba znajduje się poniżej dolnego końca, czy powyżej górnego końca zakresu.Jeśli chodzi o to, jak to działa, podstawowa idea jest dość prosta: liczba ujemna, gdy jest postrzegana jako liczba bez znaku, będzie większa niż cokolwiek, co początkowo było liczbą dodatnią.
W praktyce ta metoda tłumaczy
number
i interwał do punktu początkowego i sprawdza, czynumber
jest w interwale[0, D]
, gdzieD = upper - lower
. Jeżelinumber
poniżej dolnej granicy: ujemny , a jeśli powyżej górnej granicy: większy niżD
.źródło
lower <= x & x <= upper
(zamiastlower <= x && x <= upper
) spowoduje to również lepszą wydajność?Rzadko można dokonać znacznych optymalizacji kodu na tak małą skalę. Duży wzrost wydajności wynika z obserwacji i modyfikacji kodu z wyższego poziomu. Możesz być w stanie całkowicie wyeliminować potrzebę testu zasięgu lub wykonać O (n) z nich zamiast O (n ^ 2). Być może będziesz w stanie ponownie zamówić testy, aby zawsze sugerować jedną stronę nierówności. Nawet jeśli algorytm jest idealny, zyski są bardziej prawdopodobne, gdy zobaczysz, jak ten kod testuje zakres 10 milionów razy i znajdujesz sposób na grupowanie ich i użycie SSE do przeprowadzenia wielu testów równolegle.
źródło
To zależy od tego, ile razy chcesz wykonać test na tych samych danych.
Jeśli wykonujesz test jednorazowo, prawdopodobnie nie ma znaczącego sposobu na przyspieszenie algorytmu.
Jeśli robisz to dla bardzo skończonego zestawu wartości, możesz utworzyć tabelę odnośników. Wykonywanie indeksowania może być droższe, ale jeśli zmieścisz całą tabelę w pamięci podręcznej, możesz usunąć wszystkie rozgałęzienia z kodu, co powinno przyspieszyć.
Dla twoich danych tabela odnośników wynosiłaby 128 ^ 3 = 2 097 152. Jeśli możesz kontrolować jedną z trzech zmiennych, aby rozważyć wszystkie instancje
start = N
jednocześnie, wówczas rozmiar zestawu roboczego spada do128^2 = 16432
bajtów, co powinno dobrze pasować w większości nowoczesnych pamięci podręcznych.Nadal będziesz musiał przeprowadzić analizę porównawczą rzeczywistego kodu, aby sprawdzić, czy tabela wyszukiwania bez rozgałęzień jest wystarczająco szybsza niż oczywiste porównania.
źródło
bool between[start][end][x]
. Jeśli wiesz, jak będzie wyglądał twój wzorzec dostępu (na przykład x rośnie monotonicznie), możesz zaprojektować tabelę tak, aby zachowała lokalizację, nawet jeśli cały stół nie mieści się w pamięci.Ta odpowiedź ma na celu raportowanie badań wykonanych z zaakceptowaną odpowiedzią. Przeprowadziłem test z zamkniętym zakresem na dużym wektorze posortowanej losowej liczby całkowitej i ku mojemu zdziwieniu podstawowa metoda (low <= num && num <= high) jest w rzeczywistości szybsza niż zaakceptowana powyżej odpowiedź! Test został przeprowadzony na HP Pavilion g6 (AMD A6-3400APU z 6 GB pamięci RAM. Oto podstawowy kod używany do testowania:
w porównaniu z poniższą, która jest zaakceptowaną odpowiedzią powyżej:
Zwróć uwagę, że randVec jest posortowanym wektorem. Dla dowolnej wielkości MaxNum pierwsza metoda pokonuje drugą na moim komputerze!
źródło
W celu sprawdzenia dowolnego zakresu zmiennych:
Szybsza jest operacja bitowa:
Spowoduje to zmniejszenie dwóch gałęzi do jednego.
Jeśli zależy Ci na bezpiecznym typie:
Możesz połączyć więcej sprawdzania zmiennego zakresu razem:
Spowoduje to zmniejszenie 4 gałęzi do 1.
Jest 3,4 razy szybszy niż stary w gcc:
źródło
Czy nie jest możliwe wykonanie bitowej operacji na liczbie całkowitej?
Ponieważ musi on zawierać się w przedziale od 0 do 128, jeśli ustawiony jest 8. bit (2 ^ 7), wynosi on 128 lub więcej. Sprawa krawędzi będzie jednak uciążliwa, ponieważ chcesz włączyć porównanie.
źródło
x <= end
, gdzieend <= 128
. Niex <= 128
.