Pracuję prawie wyłącznie w C ++ 11/14, i zwykle kulą się, gdy widzę taki kod:
std::int64_t mArray;
mArray |= someMask << 1;
To tylko przykład; Mówię ogólnie o manipulacji bitami. Czy w C ++ jest naprawdę sens? Powyższe jest zniekształcające i podatne na błędy, a użycie std::bitset
pozwala na:
- łatwiej modyfikować rozmiar według
std::bitset
potrzeb, dostosowując parametr szablonu i pozwalając implementacji zająć się resztą, oraz - poświęcaj mniej czasu na zastanawianie się, co się dzieje (i być może popełnianie błędów) i pisz
std::bitset
w sposób podobny dostd::array
innych kontenerów danych.
Moje pytanie brzmi; czy jest jakiś powód, aby nie używać std::bitset
typów prymitywnych, innych niż kompatybilność wsteczna?
c++
c++11
bitwise-operators
ilościowo
źródło
źródło
std::bitset
jest ustalany w czasie kompilacji. To jedyna wada, o której myślę.std::bitset
manipulacji bitami w stylu c (np.int
), która jest również naprawiona w czasie kompilacji.std::bitset
nie był dostępny (lub znany autorowi) i nie było powodu, aby przepisać kod, aby go użyćstd::bitset
.bitset
jeden jest, ale mały wektor lub zestawint
s (indeksu bitów) również może być uzasadniony. Filozofia C / C ++ nie ukrywa złożoności wyboru przed programistą.Odpowiedzi:
Z logicznego (nietechnicznego) punktu widzenia nie ma przewagi.
Każdy zwykły kod C / C ++ może być zawinięty w odpowiedni „konstrukt biblioteki”. Po takim opakowaniu kwestia „czy to jest bardziej korzystne niż to” staje się dyskusyjnym pytaniem.
Z punktu widzenia szybkości C / C ++ powinien umożliwiać konstruowaniu biblioteki generowanie kodu, który jest tak samo wydajny, jak zwykły kod, który pakuje. Jest to jednak uzależnione od:
Używając tego rodzaju nietechnicznego argumentu, wszelkie „brakujące funkcje” mogą być dodawane przez każdego i dlatego nie są liczone jako wady.
Jednak wbudowanych wymagań i ograniczeń nie można obejść za pomocą dodatkowego kodu. Poniżej argumentuję, że rozmiar
std::bitset
jest stałą czasową kompilacji, a zatem, chociaż nie jest liczony jako wada, wciąż jest to coś, co wpływa na wybór użytkownika.Z estetycznego punktu widzenia (czytelność, łatwość konserwacji itp.) Istnieje różnica.
Jednak nie jest oczywiste, że
std::bitset
kod natychmiast wygrywa nad zwykłym kodem C. Należy przyjrzeć się większym fragmentom kodu (a nie próbce zabawki), aby stwierdzić, czy użyciestd::bitset
poprawiło ludzką jakość kodu źródłowego.Szybkość manipulacji bitami zależy od stylu kodowania. Styl kodowania wpływa zarówno na manipulację bitami C / C ++, jak również ma zastosowanie
std::bitset
, jak wyjaśniono poniżej.Jeśli ktoś pisze kod, który używa
operator []
do odczytu i zapisu jeden bit na raz, będzie musiał to zrobić wiele razy, jeśli będzie więcej niż jeden bit do manipulacji. To samo można powiedzieć o kodzie w stylu C.Jednak
bitset
ma też inne podmioty, takie jakoperator &=
,operator <<=
itp, które działa na pełnej szerokości bitset. Ponieważ leżące u jego podstaw maszyny mogą często działać w trybie 32-bitowym, 64-bitowym, a czasem 128-bitowym (z SIMD) na raz (w tej samej liczbie cykli procesora), kod zaprojektowany jest tak, aby korzystać z takich operacji wielobitowych może być szybszy niż „pętlowy” kod manipulacji bitami.Ogólna idea nazywa się SWAR (SIMD w rejestrze) i jest podtematem przy manipulowaniu bitami.
Niektórzy dostawcy C ++ mogą implementować
bitset
SIMD między 64-bitami a 128-bitami. Niektórzy dostawcy mogą tego nie robić (ale mogą to zrobić). Jeśli trzeba wiedzieć, co robi biblioteka dostawcy C ++, jedynym sposobem jest przyjrzenie się dezasemblacji.Jeśli chodzi o to, czy
std::bitset
ma ograniczenia, mogę podać dwa przykłady.std::bitset
musi być znany w czasie kompilacji. Aby stworzyć tablicę bitów o dynamicznie wybranym rozmiarze, trzeba będzie użyćstd::vector<bool>
.std::bitset
nie zapewnia sposobu wyodrębnienia kolejnego wycinka N bitów z większegobitset
z M bitów.Pierwszy ma podstawowe znaczenie, co oznacza, że ludzie, którzy potrzebują dynamicznych zestawów bitów, muszą wybrać inne opcje.
Drugi można pokonać, ponieważ można napisać jakieś adaptery do wykonania zadania, nawet jeśli standard
bitset
nie jest rozszerzalny.Istnieją pewne typy zaawansowanych operacji SWAR, które nie są dostarczane od razu po wyjęciu z pudełka
std::bitset
. Na tej stronie można przeczytać o tych operacjach dotyczących permutacji bitów . Jak zwykle można je wdrożyć samodzielnie, działając na zasadziestd::bitset
.Odnośnie dyskusji na temat wydajności.
Jedno ostrzeżenie: wiele osób pyta, dlaczego (coś) ze standardowej biblioteki jest znacznie wolniejsze niż jakiś prosty kod w stylu C. Nie powtórzyłbym tutaj wstępnej wiedzy na temat znakowania mikrobenchowania, ale mam tylko następującą radę: upewnij się, że test porównawczy w „trybie zwolnienia” (z włączonymi optymalizacjami) i upewnij się, że kod nie jest eliminowany (eliminacja martwego kodu) lub nie jest wyciągnięty z pętli (niezmienny ruch kodu) .
Ponieważ generalnie nie jesteśmy w stanie stwierdzić, czy ktoś (w Internecie) prawidłowo robił znaki mikrodruku, jedynym sposobem na uzyskanie wiarygodnego wniosku jest zrobienie własnych znaków mikrodruku i udokumentowanie szczegółów oraz poddanie się publicznej ocenie i krytyce. Ponowne wykonywanie mikrodruków, które inni robili wcześniej, nie zaszkodzi.
źródło
std::bitset
. Nie ma gwarancji zgodności pamięci (wstd::bitset
), co oznacza, że nie powinna być dzielona między rdzeniami. Ludzie, którzy muszą udostępniać go między rdzeniami, będą mieli tendencję do budowania własnej implementacji. Gdy dane są współużytkowane przez różne rdzenie, zwykle wyrównuje się je do granicy linii pamięci podręcznej. Niezastosowanie się do tego zmniejsza wydajność i naraża na więcej pułapek bezatomowych. Nie mam wystarczającej wiedzy, aby dać przegląd tego, jak zbudować równoległą implementacjęstd::bitset
.bitset
wolą.Z pewnością nie ma to zastosowania we wszystkich przypadkach, ale czasami algorytm może zależeć od wydajności kręcenia bitów w stylu C, aby zapewnić znaczny wzrost wydajności. Pierwszym przykładem, jaki przychodzi mi do głowy, jest użycie bitboardów , sprytnego kodowania liczb całkowitych pozycji w grze planszowej, do przyspieszenia silników szachowych i tym podobnych. Tutaj ustalony rozmiar typów liczb całkowitych nie stanowi problemu, ponieważ i tak szachownice mają zawsze wartość 8 * 8.
Dla prostego przykładu rozważ następującą funkcję (zaczerpniętą z tej odpowiedzi Bena Jacksona ), która testuje pozycję Connect Four pod kątem zwycięstwa:
źródło
std::bitset
będzie wolniej?