boost :: dynamic_bitset медленнее, чем std :: bitset, если только std :: bitset не сброшен

Недавно я наткнулся на шаблоны битов и очень хотел бы использовать их в своем текущем проекте. Читая дальше, я вижу, что std::bitset шаблон должен иметь размер, определенный во время компиляции. Многие предлагают использовать boost::dynamic_bitset чтобы смягчить это требование.

Чтобы сравнить два, я решил сделать сравнение скорости set, flip, а также count методы.

Результаты довольно странные … и мне интересно, если кто-то может пролить свет на это для меня.

Код находится в конце поста, но я объясню, что я здесь делаю. у меня есть такой std::bitset объект (назовите это bs) и один boost::dynamic_bitset объект (назовите это dynbs). У каждого есть n=1000000 биты. Для данного метода выше, вызовите метод на каждом из n биты последовательно и повторите это R=10000 раз.

С использованием std::chrono библиотека, вот время для каждого в наносекундах:

set
bitset:              267 nsecs
dyn bitset:      18603174546 nsecs

flip
bitset:               73 nsecs
dyn bitset:      18842352867 nsecs

count
bitset:               77 nsecs
dyn bitset:               51 nsecs

boost::dynamic_bitset кажется гораздо медленнее set а также flip,

Чтобы было интереснее, если метод reset вызывается на двух объектах перед запуском этих тестов, тогда время сопоставимо. Вот они:

set
bitset:      19397779399 nsecs
dyn bitset:      18472863864 nsecs

flip
bitset:      18599248629 nsecs
dyn bitset:      18376267939 nsecs

count
bitset:               68 nsecs
dyn bitset:               61 nsecs

Теперь оба контейнера утверждают, что инициализируют все биты 0таким образом, призыв к reset не должен менять ни один из битов. Сбрасывает вывод none до и после reset действительно подтверждает это.

После всего этого у меня есть два вопроса:

1) Почему boost::dynamic_bitset намного медленнее, чем std::bitset при звонке set а также flip?

2) Почему звонит reset оказывают огромное негативное влияние на скорость std::bitset?

Вот мой код:

#include <iostream>
#include <iomanip>
#include <bitset>
#include <boost/dynamic_bitset.hpp>
#include <vector>
#include <chrono>
#include <ctime>

using namespace std;
using namespace chrono;
using namespace boost;

int main(){
const unsigned int n=1000000;
bitset< n > bs;
dynamic_bitset< > dynbs(n);
// bs.reset();
// dynbs.reset();

unsigned int i,r,R=10000;
high_resolution_clock::time_point tick,tock;////////////////////////////////////////////////////////////
// Method: set

std::cout << "set" << std::endl;

tick=high_resolution_clock::now();

for(r=0; r<R; r++)
for(i=0; i<n; i++)
bs.set(i);

tock=high_resolution_clock::now();
cout << setw(16) << "bitset: "<< setw(16) << duration_cast<nanoseconds>(tock-tick).count() << " nsecs"<< std::endl;

tick=high_resolution_clock::now();

for(r=0; r<R; r++)
for(i=0; i<n; i++)
dynbs.set(i);

tock=high_resolution_clock::now();
cout << setw(16) << "dyn bitset: "<< setw(16) << duration_cast<nanoseconds>(tock-tick).count() << " nsecs"<< std::endl << std::endl;////////////////////////////////////////////////////////////
// Method: flip

std::cout << "flip" << std::endl;

tick=high_resolution_clock::now();

for(r=0; r<R; r++)
for(i=0; i<n; i++)
bs.flip(i);

tock=high_resolution_clock::now();
cout << setw(16) << "bitset: "<< setw(16) << duration_cast<nanoseconds>(tock-tick).count() << " nsecs"<< std::endl;

tick=high_resolution_clock::now();

for(r=0; r<R; r++)
for(i=0; i<n; i++)
dynbs.flip(i);

tock=high_resolution_clock::now();
cout << setw(16) << "dyn bitset: "<< setw(16) << duration_cast<nanoseconds>(tock-tick).count() << " nsecs"<< std::endl << std::endl;////////////////////////////////////////////////////////////
// Method: count

std::cout << "count" << std::endl;

tick=high_resolution_clock::now();

for(r=0; r<R; r++)
for(i=0; i<n; i++)
bs.count();

tock=high_resolution_clock::now();
cout << setw(16) << "bitset: "<< setw(16) << duration_cast<nanoseconds>(tock-tick).count() << " nsecs"<< std::endl;

tick=high_resolution_clock::now();

for(r=0; r<R; r++)
for(i=0; i<n; i++)
dynbs.count();

tock=high_resolution_clock::now();
cout << setw(16) << "dyn bitset: "<< setw(16) << duration_cast<nanoseconds>(tock-tick).count() << " nsecs"<< std::endl;return 0;
}

Я скомпилировал это с

g++ -O3 -std=c++11 bitset.cpp -o bitset

где bitset.cpp это код, вставленный выше.

11

Решение

1) Почему boost::dynamic_bitset намного медленнее, чем
std::bitset при звонке ставить и переворачивать?

поскольку std::bitset не использует динамическое распределение, и ваш bitset является локальной переменной, компилятор может легко определить, что все setи flipс и countне имеют внешне видимого эффекта. В результате это оптимизирует их всех, и ваш код в конечном итоге становится кучей вызовов времени и печати.

Обратите внимание, что в приведенной выше сборке он даже не выделяет пространство стека для набора битов. Все это в основном исчезло без следа.

boost::dynamic_bitset динамически распределяет свой буфер new, который заканчивает тем, что звонил ::operator new(), которая может быть произвольной перегруженной версией, определенной в другом модуле перевода. Поэтому компилятору очень сложно доказать, что изменения в возвращаемом буфере не видны извне.

Ваш count петли для обоих std::bitset а также boost::dynamic_bitset оптимизированы, потому что компилятор может легко увидеть, что count() ничего не меняет в наборах битов, и вы не используете возвращаемое значение.

2) Почему звонит reset оказывают огромное негативное влияние на скорость
станд :: BITSET?

Я проверил исходный код reset в GCC, и это вызывает встроенный компилятор __builtin_memset, передав ему указатель на буфер. Когда вы передаете указатель на переменную стека во внешнюю функцию, то компилятор гораздо более ограничен в том, что он может удалить, поскольку изменения в переменной теперь могут быть внешне наблюдаемыми (например, вызываемая функция могла сохранить копию указатель где-то и что-то может заглянуть в него позже).

22

Другие решения

Ну, похоже, что Т.С. имеет объяснение.

Весь ваш сет и флип (и счет) на битсете полностью оптимизированы
из.

Ссылка была предоставлена ​​с кодом сборки, чтобы показать это: код сборки

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

1