Чи є якась перевага в маніпуляції з бітом в стилі c через std :: bitset?


17

Я працюю майже виключно в C ++ 11/14, і, як правило, чіпляюсь, коли бачу такий код:

std::int64_t mArray;
mArray |= someMask << 1;

Це лише приклад; Я кажу про трохи розумну маніпуляцію взагалі. Чи є в C ++ справді якийсь момент? Вищезазначене є переконливим та схильним до помилок, а використання інструменту std::bitsetдозволяє:

  1. легше змінити розмір std::bitsetнеобхідного, налаштувавши параметр шаблону та довіривши реалізації піклуватися про інше, і
  2. витрачайте менше часу на з'ясування того, що відбувається (і, можливо, помилки) і пишіть std::bitsetподібним чином до std::arrayінших контейнерів даних.

Моє запитання: Чи є якась причина, щоб не використовувати для std::bitsetпримітивних типів, окрім як для зворотної сумісності?


Розмір a std::bitset фіксується під час компіляції. Це єдиний калічний недолік, про який я можу придумати.
rwong

1
@rwong Я говорю про std::bitsetманіпуляцію з бітом у стилі c (наприклад int), який також фіксується під час компіляції.
квантовий

Однією з причин може бути застарілий код: код був написаний, коли він std::bitsetбув недоступний (або відомий автору) і не було причин переписати код на використання std::bitset.
Барт ван Інген Шенау

Я особисто думаю, що питання про те, як зробити "операції на наборі / карті / масиві бінарних змінних" легко зрозуміти всім, досі залишається невирішеним, оскільки на практиці використовується багато операцій, які не можна звести до простих операцій. Існує також занадто багато способів представити такі множини, з яких bitsetодин, але невеликий вектор або набір ints (бітового індексу) також може бути законним. Філософія C / C ++ не приховує цих складностей вибору від програміста.
rwong

Відповіді:


12

З логічної (нетехнічної) точки зору переваги немає.

Будь-який звичайний код C / C ++ може бути зафіксований у відповідній "бібліотечній конструкції". Після такого обгортання питання "чи це вигідніше, ніж це" стає суперечливим питанням.

З точки зору швидкості, C / C ++ повинен дозволити конструкції бібліотеки генерувати код, такий же ефективний, як звичайний код, який він обгортає. Однак це підлягає:

  • Функція вкладки
  • Перевірка часу на компіляцію та усунення зайвої перевірки часу виконання
  • Усунення мертвого коду
  • Багато інших оптимізацій коду ...

Використовуючи подібний нетехнічний аргумент, будь-які "відсутні функції" може бути доданий будь-ким, і тому вони не зараховуються як недолік.

Однак вбудовані вимоги та обмеження неможливо подолати додатковим кодом. Нижче я стверджую, що розмірstd::bitset - це константа часу компіляції, і, хоча він не вважається недоліком, він все-таки впливає на вибір користувача.


З естетичної точки зору (читабельність, простота обслуговування тощо) є різниця.

Однак не очевидно, що std::bitsetкод одразу перемагає звичайний код С. Треба подивитися на більші шматочки коду (а не на якийсь зразок іграшки), щоб сказати, чи std::bitsetпокращило їх використання якість вихідного коду людини.


Швидкість маніпулювання бітом залежить від стилю кодування. Стиль кодування впливає як на керування C / C ++ бітами, так і в рівній мірі застосовний std::bitset, як пояснено далі.


Якщо ви пишете код, який використовує operator [] пишете для читання і запису по одному біту за один раз, доведеться зробити це кілька разів, якщо є більше ніж один біт, яким потрібно маніпулювати. Те саме можна сказати і про код у стилі С.

Однак bitsetє й інші оператори, такі як operator &=,operator <<= тощо, який працює на повну ширину бітового набору. Оскільки основна техніка часто може працювати одночасно на 32-розрядному, 64-бітному, а іноді і 128-бітному (із SIMD) (у однаковій кількості циклів процесора), код, призначений для використання таких багатобітових операцій може бути швидше, ніж "петельний" код маніпуляції з бітами.

Загальна ідея називається SWAR (SIMD в регістрі) і є підтемою під бітовими маніпуляціями.


Деякі постачальники C ++ можуть реалізовувати bitsetміж 64-бітними та 128-бітовими SIMD-картами. Деякі постачальники можуть цього не зробити (але, можливо, з часом). Якщо потрібно знати, чим займається бібліотека постачальників C ++, єдиний спосіб - подивитися на розбирання.


Що стосується наявності std::bitsetобмежень, я можу навести два приклади.

  1. Розмір std::bitsetповинен бути відомий під час компіляції. Щоб створити масив бітів з динамічно обраним розміром, доведеться використовуватиstd::vector<bool> .
  2. Поточна специфікація C ++ для std::bitsetне забезпечує спосіб витягнути послідовний фрагмент з N бітів з більшого bitsetM біта.

Перший є фундаментальним, тобто для людей, яким потрібні бітсети динамічного розміру, вони повинні вибирати інші параметри.

Друге можна подолати, бо можна написати якісь адаптери для виконання завдання, навіть якщо стандарт bitsetне розширюється.


Існують певні типи вдосконалених операцій SWAR, які не надаються поза коробкою std::bitset. Про ці операції на цьому веб-сайті можна було прочитати про бітові перестановки . Як завжди, їх можна реалізувати самостійно, працюючи поверх std::bitset.


Щодо обговорення результативності.

Одне застереження: багато людей запитують про те, чому (щось) із стандартної бібліотеки набагато набагато повільніше, ніж якийсь простий код у стилі С. Я б не повторював тут необхідних знань про мікробенчмаркінг, але я просто маю таку пораду: переконайтесь, що орієнтуєтесь у "режимі випуску" (з увімкненими оптимізаціями) та переконайтесь, що код не усувається (усунення мертвого коду) чи не працює піднята з циклу (цикл-інваріантний рух коду) .

Оскільки загалом ми не можемо сказати, чи правильно хто-небудь (в Інтернеті) робив мікротехнічні показники, єдиним способом ми можемо отримати надійний висновок - це зробити власні мікротехнічні показники та задокументувати деталі та подати на публічний огляд та критику. Не завадить повторно робити мікро-показники, що робили інші раніше.


Випуск №2 також означає, що бітовий набір не може використовуватися в будь-якій паралельній установці, де кожен потік повинен працювати над підмножиною бітового набору.
user239558

@ user239558 Я сумніваюся, хтось хотів би паралелізувати те саме std::bitset. Не існує гарантії послідовності пам’яті (in std::bitset), що означає, що вона не повинна ділитися між ядрами. Люди, яким потрібно поділитися цим процесом, схильні будувати власну реалізацію. Коли дані поділяються між різними ядрами, прийнято вирівнювати їх до межі лінії кешу. Якщо це не зробити, це знижує продуктивність і створює більше підводних каменів без атомності. У мене не вистачає знань, щоб дати огляд того, як побудувати паралелізуючу реалізацію std::bitset.
rwong

Паралельне програмування даних зазвичай не вимагає послідовності пам'яті. Ви синхронізуєте лише між фазами. Я абсолютно хотів би паралельно обробити біт, я думаю, хто з великою bitsetволею.
user239558

@ user239558, що звучить як припускає копіювання (відповідний діапазон бітів, який обробляється кожним ядром, слід скопіювати до початку обробки). Я погоджуюся з цим, хоча думаю, що кожен, хто думає про паралелізацію, вже подумав би про те, щоб розгорнути власну реалізацію. Загалом, багато стандартних засобів бібліотеки C ++ надаються як базові реалізації; той, хто має більш серйозні потреби, збирається реалізувати свої власні.
rwong

ні немає копіювання. це просто доступ до різних частин статичної структури даних. синхронізація тоді не потрібна.
user239558

2

Це звичайно не застосовується у всіх випадках, але іноді алгоритм може залежати від ефективності скручування бітів у стилі С, щоб забезпечити значне підвищення продуктивності. Перший приклад, який мені спадає на думку, - це використання бітбордів , розумних цілих кодувань позицій настільних ігор, щоб прискорити шахові двигуни тощо. Тут фіксований розмір цілих типів не є проблемою, оскільки шахові дошки завжди є 8 * 8.

Для простого прикладу розглянемо наступну функцію (взята з цієї відповіді Бен Джексон ), яка тестує позицію Connect Four для перемоги:

// return whether newboard includes a win
bool haswon2(uint64_t newboard)
{
    uint64_t y = newboard & (newboard >> 6);
    uint64_t z = newboard & (newboard >> 7);
    uint64_t w = newboard & (newboard >> 8);
    uint64_t x = newboard & (newboard >> 1);
    return (y & (y >> 2 * 6)) | // check \ diagonal
           (z & (z >> 2 * 7)) | // check horizontal -
           (w & (w >> 2 * 8)) | // check / diagonal
           (x & (x >> 2));      // check vertical |
}

2
Як ви думаєте, std::bitsetчи буде повільніше?
Quant

1
Що ж, при швидкому погляді на джерело, біт libc ++ базується на одному size_t або масиві їх, тому, ймовірно, складеться до чогось по суті еквівалентного / ідентичного, особливо в системі, де sizeof (size_t) == 8 - так ні, це, мабуть, не буде повільніше.
Райан Павлик
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.