побитовые операции над вектором‹bool›

как лучше всего выполнять побитовые операции с vector<bool>?

насколько я понимаю, vector<bool> — это специализация, использующая один бит на логическое значение. Я выбрал vector<bool> из соображений экономии памяти. Я знаю, что есть некоторые проблемы с vector<bool>, но для моих нужд это подходит.

теперь - какой самый эффективный способ применения побитовых операций к целым таким векторам?

если я делаю это в цикле for и считываю каждое отдельное логическое значение и сохраняю его обратно, насколько я понимаю, внутри выполняется гораздо больше операций, чтобы получить доступ к фактическим значениям.

Благодарность!


person Mat    schedule 29.10.2010    source источник
comment
+1 за правильный ответ, задав неправильный вопрос.   -  person ergosys    schedule 29.10.2010


Ответы (4)


Если количество битов фиксируется во время компиляции, вам будет гораздо лучше использовать std::bitset

Если нет (т. е. количество битов меняется во время выполнения), вы должны увидеть и использовать boost::dynamic_bitset)

В обоих случаях очень легко выполнять все побитовые операции.

person Arun    schedule 29.10.2010
comment
+1, потому что я бы использовал эти библиотеки, если бы решил, что могу взять зависимости для проекта, над которым я работал. - person Merlyn Morgan-Graham; 29.10.2010
comment
Предоставленная ссылка на документацию по набору битов иногда изменяется и больше недействительна. Существует ссылка на текущую последнюю версию, которая станет недействительной: gcc.gnu.org/onlinedocs/libstdc++/libstdc++-api-4.5/a00396.html - person Jimbo; 22.02.2011
comment
Побитовые операции на любом из них автоматически векторизуются с помощью gcc и clang, выполняя 16 или 32 байта за раз. Они очень хороши. godbolt.org/g/cnRFEG (Но boost::dynamic_bitset имеет довольно плохой .count(), используя даже байт-LUT когда доступны аппаратные popcnt и инструкции AVX2. std::bitset также имеет хорошую .count() производительность.) - person Peter Cordes; 31.07.2017

Игнорируя заголовок вашего вопроса, давайте вместо этого ответим на этот вопрос:

как лучше всего выполнять побитовые операции с вектором?

Лучший способ — определить ваш вектор как vector<unsigned char> (или vector<uint32_t>, или любой другой целочисленный тип, который вы выберете) и выполнять побитовые операции, как обычно, для массива целых чисел без знака. Так все будет намного быстрее, и не будет никакого скрытого механизма.

Вы можете использовать деление (или побитовые операторы, если вы ловкий), чтобы решить, с каким индексом массива вам нужно работать, и циклы for для применения побитовых операций, превышающих один элемент.

Вот связанный с этим вопрос: Bit переворачивает много битов в С

В основном вы будете выполнять те же самые операции, если и когда решите обернуть vector<unsigned some-int-type> своими собственными операторами.

person Merlyn Morgan-Graham    schedule 29.10.2010
comment
поэтому, если я все еще хочу иметь возможность напрямую обращаться к своим логическим значениям, мне действительно нужно инкапсулировать вектор «без знака char» и сделать вектор механики «буль» сам, просто дополнительно также предоставляя функциональность для побитовых операций? - person Mat; 29.10.2010
comment
@Mat: Вам не нужно ничего делать, но для достижения максимальной производительности я предлагаю делать то, что вы только что описали. Оберните все это в класс и реализуйте для него перегруженные операторы. Затем вы можете оптимизировать на свое усмотрение. На самом деле это не очень сложный код для реализации, и он обеспечивает правильный баланс абстракции и производительности. - person Merlyn Morgan-Graham; 29.10.2010
comment
на самом деле я просто хотел узнать, может быть, эта функциональность уже не существует? потому что мне кажется логичным, что это то, что вы хотите сделать с битвекторами - person Mat; 29.10.2010
comment
@Mat: Я думаю, что я читал, что люди считают специализацию на vector<bool> плохой идеей, поэтому комитет по стандартам, возможно, решил не бросать хорошие деньги после плохих. Я бы не сильно удивился, если бы существовала сторонняя библиотека, но в ней нет ничего стандартного. - person Merlyn Morgan-Graham; 29.10.2010
comment
@Mat: Если вы можете использовать стороннюю библиотеку, идите за ответом ArunSaha :) - person Merlyn Morgan-Graham; 29.10.2010

Я прочитал оба этих ответа, но просто хотел быстрого решения и реализовал что-то ужасное.

Вы можете заставить побитовые операторы работать с vector<bool>, но код должен быть специализирован для реализации стандартной библиотеки С++ или вернуться к медленной форме. Вот мой operator| для GNU libstdc++-v3:

std::vector<bool> operator|(std::vector<bool> A, const std::vector<bool>& B)
{
    if (A.size() != B.size())
        throw std::invalid_argument("differently sized bitwise operands");

    std::vector<bool>::iterator itA = A.begin();
    std::vector<bool>::const_iterator itB = B.begin();

    // c++ implementation-specific
    while (itA < A.end())
        *(itA._M_p ++) |= *(itB._M_p ++); // word-at-a-time bitwise operation

    return A;
}

Это конечно очень плохо. Кто-то обновляет GCC, новая версия хранит вещи по-другому, и ваш код ломается без видимой причины.

person fuzzyTew    schedule 28.08.2011
comment
К сожалению, это не автоматически векторизуется с помощью gcc или clang для x86-64 с -O3 -march=haswell. godbolt.org/g/eJnfKp. Он должен использовать AVX2 для vpor 32 байтов за раз, а не только 8 за раз со скалярным or. dynamic_bitset<> Boost выполняет автоматическую векторизацию для a & b. Но, к сожалению, функция Boost .count() не очень эффективна. (Но намного лучше, чем std::count на vector<bool>, если только вы не используете clang -stdlib=libc++, в этом случае векторизация выполняется с помощью AVX2.) - person Peter Cordes; 31.07.2017
comment
Во всяком случае, я не нашел способа получить хороший код для popcount результата логической операции между двумя битовыми векторами динамического размера, кроме ручной векторизации, конечно. - person Peter Cordes; 31.07.2017
comment
@PeterCordes — clang можно уговорить сделать что-то хорошее для битовых операций (and в этом примере) используя временный массив в стеке. Сохранение в стеке и последующая перезагрузка на самом деле не являются ужасным способом получить 4 значения из ymm в место, где их может использовать popcnt. Операции mov r9, rcx; or r9, 8 чередуются с векторизованными, это немного глупо и может стоить немного пропускной способности (почему они просто не использовали смещение в режимах адресации или, по крайней мере, не сделали это с одним add?). - person BeeOnRope; 01.08.2017
comment
Не разворачивайте popcnt достаточно, чтобы избежать задержки из-за ложной зависимости (и добавить дополнительный цикл из-за того, как они его накапливают), но, вероятно, он достигает более 50% производительности настроенного вручную. , что довольно хорошо. Что касается настроенного вручную, мне не ясно, что лучше: pshufb подход к подсчету кусочков или popcnt, или, возможно, их комбинация. Вероятно, первое, поскольку вывод уже находится в формате ymm. - person BeeOnRope; 01.08.2017
comment
@BeeOnRope: на Haswell/Skylake распаковка AVX2 в фрагменты, затем vpshufb -> vpaddb выполняется быстрее, чем скалярная popcnt для больших массивов. (Каждые 255 или меньше итераций используйте vpsadbw для hsum до 64-бит). Особенно когда вы хотите popcnt(A[] & B[])< /a>, AVX2 — это большая победа. Но получить авто-векторизованный C++ для компиляции во что-то, что делает vpand-›shift/mask-›2xvpshufb-›vpaddb без сохранения/перезагрузки между AND и popcount, может оказаться невозможным. - person Peter Cordes; 01.08.2017
comment
@BeeOnRope: В этом сообщении в блоге есть номера HSW и SKL для AVX2 vpshufb vs. popcnt для различных размеров массивов, только для проблемы с количеством всплывающих окон, а не для двух растровых изображений на лету. Используя написанный от руки ассемблер этого парня, AVX2 на 25% быстрее для больших буферов. - person Peter Cordes; 01.08.2017
comment
Что бы это ни стоило, clang в некоторых случаях заменяет __builtin_popcount на версию AVX, но не в коде, который я привел выше. Неудивительно, что popcnt AVX2 быстрее, поскольку имеет максимальную пропускную способность 128 бит за цикл, ограниченную одним pshufb (32 полубайта), по сравнению с 64 битами за цикл для popcnt, ограниченной только выдачей на один порт. Конечно, на Ryzen все наоборот, и popcnt может выдавать 4 разряда по 256 бит за такт. Так что код clang, вероятно, не так уж и страшен (а у Ryzen может и не быть ложной деп). @ПитерКордес - person BeeOnRope; 01.08.2017
comment
@BeeOnRope: Ryzen по-прежнему может выполнять только 2 загрузки за цикл, поэтому хитрость заключается в том, чтобы поддерживать его (особенно если вы хотите подсчитать И двух растровых изображений). Может быть, с сочетанием скалярных нагрузок и movdqa->movq+pextrq? Хм, pextrq это 2 мкп. Может быть, просто гибридная стратегия popcnt + AVX2, чередующаяся в правильном соотношении для максимальной пропускной способности ALU с AVX2 и максимальной пропускной способности исходящей нагрузки с popcnt rax, [mem]+add rdx, rax. - person Peter Cordes; 01.08.2017
comment
@PeterCordes - верно, хорошая мысль. Ну, мы можем сказать, что по крайней мере popcnt может легко обрабатывать 128-бит/цикл из памяти за счет 2 операций, поэтому, вероятно, это всегда лучше, чем чистый подход AVX2 (за исключением случаев, когда входные данные уже находятся в регистрах AVX2), но вы могли бы быть в состоянии выжать немного больше пропускной способности с гибридным подходом. - person BeeOnRope; 01.08.2017

Этот тоже должен работать.

std::vector<bool> v3(v1.size());
std::transform(v1.begin(), v1.end(), 
               v2.begin(), v3.begin(), std::logical_and<bool>());
person Sphingidae    schedule 25.03.2012
comment
Это будет невероятно неэффективно, потому что каждый бит нужно извлечь из своего слова, затем преобразовать, а затем вставить обратно в слово. Решения, которые работают со словом за раз, будут намного эффективнее. - person Joel; 03.03.2015
comment
@Joel: Стандартные библиотеки могут и должны специализировать свои шаблоны std алгоритмов для vector<bool>. libc++ делает для некоторых вещей (например, std::count на vector<bool> автоматически векторизуется в очень хороший код AVX2). Но в этом случае clang и gcc с libc++ и libstdc++ создают ужасный код, зацикливаясь понемногу и даже не выполняя эффективно И. (Проверка бита отдельно в каждом входе и использование условных переходов! godbolt.org/g/4MDd8v) (Даже если вы используете std::bit_and вместо logical_and). - person Peter Cordes; 31.07.2017
comment
В любом случае, этот ответ технически неплох, он просто выявляет МАССИВНЫЕ пропущенные оптимизации компиляторами и авторами стандартных библиотек. Это означает, что на практике вы пока не должны его использовать. - person Peter Cordes; 31.07.2017