Недавно я наткнулся на шаблоны битов и очень хотел бы использовать их в своем текущем проекте. Читая дальше, я вижу, что 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
это код, вставленный выше.
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
, передав ему указатель на буфер. Когда вы передаете указатель на переменную стека во внешнюю функцию, то компилятор гораздо более ограничен в том, что он может удалить, поскольку изменения в переменной теперь могут быть внешне наблюдаемыми (например, вызываемая функция могла сохранить копию указатель где-то и что-то может заглянуть в него позже).
Ну, похоже, что Т.С. имеет объяснение.
Весь ваш сет и флип (и счет) на битсете полностью оптимизированы
из.
Ссылка была предоставлена с кодом сборки, чтобы показать это: код сборки
Вернув значения из трех разных методов и добавив их в дополнительную переменную, добились цели, и теперь это кажется честной борьбой (как предложено Т.С.).