Jaki jest najbardziej wydajny algorytm do osiągnięcia następujących celów:
0010 0000 => 0000 0100
Konwersja odbywa się z MSB-> LSB do LSB-> MSB. Wszystkie bity muszą być odwrócone; to znaczy, nie jest to zamiana endianizmu.
c
algorithm
bit-manipulation
green_t
źródło
źródło
Odpowiedzi:
UWAGA : Wszystkie poniższe algorytmy są w języku C, ale powinny być przenośne na wybrany język (po prostu nie patrz na mnie, gdy nie są tak szybkie :)
Opcje
Niska pamięć (
int
maszyna 32-bitowa , 32-bitowa) ( stąd ):Ze słynnej strony Bit Twiddling Hacks :
Najszybszy (tabela odnośników) :
Możesz rozszerzyć ten pomysł do 64-bitów
int
lub wymienić pamięć na szybkość (zakładając, że pamięć podręczna danych L1 jest wystarczająco duża) i odwrócić 16 bitów na raz za pomocą tabeli wyszukiwania 64-wejściowej.Inne
Prosty
Szybszy (procesor 32-bitowy)
Szybszy (procesor 64-bitowy)
Jeśli chcesz to zrobić na 32-bitach
int
, po prostu odwróć bity w każdym bajcie i odwróć kolejność bajtów. To jest:Wyniki
Porównywałem dwa najbardziej obiecujące rozwiązania, tablicę odnośników i bitowe-AND (pierwsze). Maszyna testowa to laptop z 4 GB pamięci DDR2-800 i Core 2 Duo T7500 @ 2,4 GHz, 4 MB pamięci podręcznej L2; YMMV. Użyłem gcc 4.3.2 na 64-bitowym systemie Linux. OpenMP (i powiązania GCC) zastosowano do timerów o wysokiej rozdzielczości.
rewers. c
reverse_lookup.c
Wypróbowałem oba podejścia przy kilku różnych optymalizacjach, przeprowadziłem 3 próby na każdym poziomie, a każda próba odwróciła 100 milionów losowo
unsigned ints
. W przypadku opcji tabeli odnośników wypróbowałem oba schematy (opcje 1 i 2) podane na stronie hacków bitowych. Wyniki pokazano poniżej.Bitowe AND
Tabela przeglądowa (opcja 1)
Tabela przeglądowa (opcja 2)
Wniosek
Skorzystaj z tabeli odnośników z opcją 1 (adresowanie bajtów jest zaskakująco wolne), jeśli martwisz się wydajnością. Jeśli chcesz wycisnąć z systemu każdy ostatni bajt pamięci (a możesz, jeśli zależy Ci na wydajności odwracania bitów), zoptymalizowane wersje bitowego AND są też nieznośne.
Zastrzeżenie
Tak, wiem, że kod testu porównawczego to kompletny hack. Sugestie, jak to poprawić, są mile widziane. Co wiem o:
ld
wysadził się z pewnym błędem redefinicji symboli), więc nie wierzę, że wygenerowany kod jest dostrojony dla mojej mikroarchitekty.32-bitowy
EDYCJA: Próbowałem też używać
uint64_t
typów na moim komputerze, aby sprawdzić, czy nastąpił wzrost wydajności. Wydajność była o około 10% szybsza niż 32-bitowa i była prawie identyczna, niezależnie od tego, czy używałeś typów 64-bitowych do odwrócenia bitów na dwóchint
typach 32-bitowych na raz, czy też faktycznie odwracałeś bity o połowę więcej niż 64- wartości bitowe. Kod asemblacji pokazano poniżej (w pierwszym przypadku odwrócenie bitów dla dwóch 32-bitowychint
typów jednocześnie):źródło
Ten wątek przykuł moją uwagę, ponieważ dotyczy prostego problemu, który wymaga dużo pracy (cykli procesora), nawet w przypadku nowoczesnego procesora. I pewnego dnia stałem tam również z tym samym problemem ¤ #% "#". Musiałem przerzucić miliony bajtów. Wiem jednak, że wszystkie moje systemy docelowe są oparte na procesorach Intela, więc zacznijmy optymalizować do maksimum !!!
Więc użyłem kodu odnośnika Matta J jako podstawy. System, na którym przeprowadzam testy to i7 haswell 4700eq.
Wyszukiwanie bitów przez Matta J 400 000 000 bajtów: około 0,272 sekundy.
Potem poszedłem naprzód i spróbowałem sprawdzić, czy kompilator ISPC Intela może wektoryzować arytmetykę na odwrocie. C.
Nie będę cię tu nudził swoimi odkryciami, ponieważ dużo próbowałem, aby pomóc kompilatorowi znaleźć rzeczy, w każdym razie osiągnąłem wydajność około 0,15 sekundy do 400 000 000 bajtów bitflipa. To świetna redukcja, ale dla mojej aplikacji jest to wciąż zdecydowanie zbyt powolne ...
Więc ludzie pozwalają mi zaprezentować najszybszy bitflipper oparty na Intelu na świecie. O godzinie:
Czas do bitflipa 400000000 bajtów: 0,050082 sekund !!!!!
Printf służą do debugowania ...
Oto koń roboczy:
Kod zajmuje 32 bajty, a następnie maskuje skubki. Wysoki skrawek zostaje przesunięty w prawo o 4. Następnie używam vpshufb i ymm4 / ymm3 jako tabel odnośników. Mógłbym użyć pojedynczej tabeli wyszukiwania, ale musiałbym przesunąć w lewo, zanim OR ponownie skubię razem.
Są jeszcze szybsze sposoby odwracania bitów. Ale jestem związany z jednym wątkiem i procesorem, więc był to najszybszy jaki mogłem osiągnąć. Czy możesz zrobić szybszą wersję?
Proszę nie komentować używania komend Intraninsic Equivalent kompilatora Intel C / C ++ ...
źródło
pshub
, ponieważ przecież najlepsze popcount też jest z tym zrobione! Napisałbym to tutaj, gdyby nie ty. Sława.popcnt
,tzcnt
ipext
wszystkie na porcie 1. Tak więc każdapext
lubtzcnt
kosztuje Ciępopcnt
przepustowość. Jeśli twoje dane są gorące w pamięci podręcznej L1D, najszybszym sposobem na policzenie macierzy na procesorach Intel jest użycie AVX2 pshufb. (Ryzen mapopcnt
przepustowość 4 na zegar, więc jest to prawdopodobnie optymalne, ale rodzina buldożerów mapopcnt r64,r64
przepustowość na 4 zegary ... agner.org/optimize ).To kolejne rozwiązanie dla osób kochających rekurencję.
Pomysł jest prosty. Podziel wejście na pół i zamień dwie połówki, kontynuuj, aż osiągnie pojedynczy bit.
Oto funkcja rekurencyjna, aby ją rozwiązać. (Uwaga: Użyłem niepodpisanych liczb całkowitych, więc może pracować dla danych wejściowych o rozmiarze do sizeof (niepodpisanych liczb wewnętrznych) * 8 bitów.
To jest wynik:
źródło
numBits
int, kiedy podzielisz 3 przez 2 dla parametru funkcji, zostanie on zaokrąglony w dół do 1?Cóż, z pewnością nie będzie to odpowiedź taka jak Matt J, ale mam nadzieję, że nadal będzie przydatna.
Jest to dokładnie ten sam pomysł, co najlepszy algorytm Matta, z tą różnicą, że istnieje ta niewielka instrukcja o nazwie BSWAP, która zamienia bajty (a nie bity) liczby 64-bitowej. Zatem b7, b6, b5, b4, b3, b2, b1, b0 stają się b0, b1, b2, b3, b4, b5, b6, b7. Ponieważ pracujemy z liczbą 32-bitową, musimy przesunąć liczbę zamienionych bajtów w dół o 32 bity. To po prostu pozostawia nam zadanie zamiany 8 bitów każdego bajtu, co jest zrobione i voila! skończyliśmy.
Czas: na moim komputerze algorytm Matta działał w ciągu ~ 0,52 sekundy na próbę. Mój przebiegał przez około 0,42 sekundy na próbę. Myślę, że 20% szybszy nie jest zły.
Jeśli martwisz się o dostępność instrukcji, BSWAP Wikipedia wymienia instrukcję BSWAP jako dodaną z 80846, która pojawiła się w 1989 roku. Należy zauważyć, że Wikipedia stwierdza również, że instrukcja ta działa tylko na rejestrach 32-bitowych, co oczywiście nie jest sprawa na moim komputerze, to bardzo działa tylko na rejestrach 64-bitowych.
Ta metoda będzie działać równie dobrze dla dowolnego integralnego typu danych, dzięki czemu można ją uogólnić, przekazując żądaną liczbę bajtów:
które można następnie nazwać:
Kompilator powinien być w stanie zoptymalizować dodatkowy parametr (zakładając, że kompilator wstawia funkcję), a w takim
sizeof(size_t)
przypadku przesunięcie w prawo zostanie całkowicie usunięte. Pamiętaj, że GCC przynajmniej nie jest w stanie usunąć BSWAP i przesunąć w prawo, jeśli zostanie przekazanysizeof(char)
.źródło
unsigned long long int
co najmniej 64 bity, jak tu i tutajOdpowiedź Andersa Cedroniusa stanowi świetne rozwiązanie dla osób, które mają procesor x86 z obsługą AVX2. W przypadku platform x86 bez obsługi AVX lub platform innych niż x86 każda z poniższych implementacji powinna działać dobrze.
Pierwszy kod jest wariantem klasycznej metody partycjonowania binarnego, zakodowanej w celu maksymalnego wykorzystania idiomu shift-plus-logicznego przydatnego w różnych procesorach ARM. Ponadto wykorzystuje generowanie maski w locie, co może być korzystne dla procesorów RISC, które w innym przypadku wymagają wielu instrukcji, aby załadować każdą wartość maski 32-bitowej. Kompilatory dla platform x86 powinny używać stałej propagacji do obliczania wszystkich masek w czasie kompilacji, a nie w czasie wykonywania.
W tomie 4A „The Art of Computer Programming” D. Knuth pokazuje sprytne sposoby odwracania bitów, które nieco zaskakująco wymagają mniej operacji niż klasyczne algorytmy partycjonowania binarnego. Jeden z takich algorytmów dla 32-bitowych operandów, których nie mogę znaleźć w TAOCP, pokazano w tym dokumencie na stronie internetowej Hacker's Delight.
Korzystając z kompilatora C / C ++ kompilatora Intel 13.1.3.198, obie powyższe funkcje automatycznie wektoryzują ładnie ukierunkowane
XMM
rejestry. Można je również wektoryzować ręcznie bez większego wysiłku.W moim IvyBridge Xeon E3 1270v2, przy użyciu kodu wektoryzowanego, 100 milionów
uint32_t
słów zostało odwróconych bitów w 0,070 sekundy przy użyciubrev_classic()
i 0,068 sekund przy użyciubrev_knuth()
. Zadbałem o to, aby mój test nie był ograniczony przepustowością pamięci systemowej.źródło
brev_knuth()
? Podanie w pliku PDF z Hacker's Delight wydaje się wskazywać, że liczby te pochodzą bezpośrednio od samego Knutha. Nie mogę twierdzić, że zrozumiałem opis Knutha podstawowych zasad projektowania w TAOCP w stopniu wystarczającym do wyjaśnienia, w jaki sposób otrzymano stałe, lub w jaki sposób można przejść do wyprowadzania stałych i współczynników przesunięcia dla dowolnych rozmiarów słów.Zakładając, że masz tablicę bitów, co powiesz na to: 1. Zaczynając od MSB, wepchnij bity do stosu jeden po drugim. 2. Przebij bity z tego stosu do innej tablicy (lub tej samej tablicy, jeśli chcesz zaoszczędzić miejsce), umieszczając pierwszy wyskakujący bit w MSB i przechodząc od tego do mniej znaczących bitów.
źródło
Natywna instrukcja ARM „rbit” może to zrobić z 1 cyklem procesora i 1 dodatkowym rejestrem procesora, niemożliwym do pobicia.
źródło
To nie jest praca dla człowieka! ... ale idealny do maszyny
Jest to rok 2015, 6 lat od pierwszego pytania. Od tego czasu kompilatorzy stali się naszymi mistrzami, a nasza praca jako ludzi polega wyłącznie na ich pomocy. Więc jaki jest najlepszy sposób, aby przekazać nasze zamiary maszynie?
Odwracanie bitów jest tak powszechne, że trzeba się zastanawiać, dlaczego wciąż rosnący ISA x86 nie zawiera instrukcji, aby to zrobić za jednym razem.
Powód: jeśli podasz kompilatorowi swoje prawdziwe zwięzłe zamiary, odwrócenie bitów powinno zająć tylko ~ 20 cykli procesora . Pozwól, że pokażę ci, jak wykonać reverse () i jak go używać:
Kompilowanie tego przykładowego programu z wersją Clanga> = 3.6, -O3, -march = native (testowane z Haswell), daje kod jakości grafiki przy użyciu nowych instrukcji AVX2, z czasem działania wynoszącym 11 sekund przetwarzającym ~ 1 miliard wstecz. To ~ 10 ns na odwrót (), przy cyklu CPU .5 ns przy założeniu, że 2 GHz stawia nas na słodkich 20 cyklach procesora.
Uwaga: ten przykładowy kod powinien trwać przez kilka lat jako przyzwoity punkt odniesienia, ale w końcu zacznie pokazywać swój wiek, gdy kompilatory będą wystarczająco inteligentne, aby zoptymalizować main (), aby po prostu wydrukować końcowy wynik zamiast naprawdę obliczać cokolwiek. Ale na razie działa w pokazie reverse ().
źródło
Bit-reversal is so common...
Nie wiem o tym Pracuję z kodem, który zajmuje się danymi na poziomie bitów praktycznie każdego dnia i nie mogę sobie przypomnieć, że kiedykolwiek miałem taką konkretną potrzebę. W jakich scenariuszach potrzebujesz? - Nie to, że sam w sobie nie jest interesującym problemem.Oczywiście oczywiste źródło hakerskich bitów jest tutaj: http://graphics.stanford.edu/~seander/bithacks.html#BitReverseObvious
źródło
Wiem, że to nie C, ale asm:
Działa to z bitem przenoszenia, więc możesz także zapisywać flagi
źródło
rcl
zmienić CF navar1
, zamiast tylko tego,shl
który nie czyta flag. (Lubadc dx,dx
). Nawet z tą poprawką jest to absurdalnie wolne, przy użyciu powolnychloop
instrukcji i utrzymywaniavar1
w pamięci! Właściwie myślę, że to powinno generować dane wyjściowe w AX, ale zapisuje / przywraca starą wartość AX ponad wynik.Implementacja z małą pamięcią i najszybsza.
źródło
Cóż, jest to w zasadzie to samo co pierwsze „reverse ()”, ale jest 64-bitowe i wymaga tylko jednej natychmiastowej maski, aby załadować go ze strumienia instrukcji. GCC tworzy kod bez skoków, więc powinno to być dość szybkie.
źródło
Byłem ciekawy, jak szybki byłby oczywisty surowy obrót. Na moim komputerze (i7 @ 2600) średnia dla 1 500 150 000 iteracji wyniosła
27.28 ns
(ponad losowy zestaw 131 071 64-bitowych liczb całkowitych).Zalety: ilość potrzebnej pamięci jest niewielka, a kod prosty. Powiedziałbym też, że nie jest tak duży. Wymagany czas jest przewidywalny i stały dla każdego wejścia (128 arytmetycznych operacji SHIFT + 64 operacji logicznych AND + 64 operacji logicznych OR).
Porównałem najlepszy czas uzyskany przez @Matt J - który ma zaakceptowaną odpowiedź. Jeśli poprawnie odczytam jego odpowiedź, najlepsze, co ma, to
0.631739
sekundy na1,000,000
iteracje, co prowadzi do średniej631 ns
na obrót.Fragment kodu, którego użyłem, to ten poniżej:
źródło
Możesz użyć standardowej biblioteki szablonów. Może być wolniejszy niż wyżej wspomniany kod. Wydaje mi się jednak jaśniejsze i łatwiejsze do zrozumienia.
źródło
Ogólny
Kod C. Wykorzystując 1 bajt danych wejściowych num jako przykład.
źródło
Co powiesz na następujące:
Mały i łatwy (choć tylko 32-bitowy).
źródło
Myślałem, że to jeden z najprostszych sposobów na odwrócenie tego bitu. proszę dać mi znać, jeśli jest jakaś wada w tej logice. w zasadzie w tej logice sprawdzamy wartość bitu na pozycji. ustaw bit, jeśli wartość wynosi 1 w pozycji odwróconej.
źródło
źródło
k
jest zawsze potęgą 2, ale kompilatory prawdopodobnie tego nie udowodnią i nie zmienią go w skanowanie bitów / shift.Myślę, że następuję najprostsza metoda, jaką znam.
MSB
jest wejściem iLSB
jest wyjściem „odwróconym”:źródło
źródło
Kolejne rozwiązanie oparte na pętli, które szybko wychodzi, gdy liczba jest niska (w C ++ dla wielu typów)
lub w C dla bez znaku int
źródło
Wygląda na to, że wiele innych postów dotyczy prędkości (tj. Najlepszy = najszybszy). A co z prostotą? Rozważać:
i mam nadzieję, że sprytny kompilator zoptymalizuje dla Ciebie.
Jeśli chcesz odwrócić dłuższą listę bitów (zawierającą
sizeof(char) * n
bity), możesz użyć tej funkcji, aby uzyskać:Spowodowałoby to odwrócenie [10000000, 10101010] na [01010101, 00000001].
źródło
ith_bit = (c >> i) & 1
. Zapisz także SUB, przesuwającreversed_char
zamiast przesuwać bit, chyba że masz nadzieję, że skompiluje się na x86 dosub something
/,bts reg,reg
aby ustawić n-ty bit w rejestrze docelowym.Odwrócenie bitu w pseudokodzie
źródło -> bajt do odwrócenia b00101100 miejsce docelowe -> odwrócony, również musi być typu bez znaku, więc bit znaku nie jest propagowany w dół
kopiuj do temp, więc oryginał pozostaje nienaruszony, musi również być typu bez znaku, aby bit znaku nie był automatycznie przesuwany
LOOP8: // wykonaj 8-krotny test, jeśli bytecopy ma wartość <0 (ujemną)
źródło
Moje proste rozwiązanie
źródło
i
? Co to za stała magiczna* 4
? Czy toCHAR_BIT / 2
jestJest to wersja 32-bitowa, musimy zmienić rozmiar, jeśli weźmiemy pod uwagę 8 bitów.
Odczytywanie wejściowej liczby całkowitej „num” w kolejności LSB-> MSB i zapisywanie w num_reverse w kolejności MSB-> LSB.
źródło
źródło