std::bitset
在查询单个位时比std::vector<bool>
更快。这怎么可能呢?如果
std::bitset
允许任意大小,就像std::vector
一样,它们如何在实现上有显著差异呢?std::bitset
在查询单个位时比std::vector<bool>
更快。这怎么可能呢?std::bitset
允许任意大小,就像std::vector
一样,它们如何在实现上有显著差异呢?据Visual Studio 2010的测量结果显示,std::bitset
通常不比std::vector<bool>
更快。我无法确定确切的原因——只知道bitset
与vector
完全特化的实现方式有明显差异。
std::bitset
通过位存储其全部内容。
template<size_t _Bits>
class bitset .....
_Ty _Array[_Words + 1]; // the set of bits
};
由于数组的大小,较大的位集不适合放在堆栈中,这并不是性能上的问题。
vector<bool>
不受堆栈问题的影响,在测试大小为1e6和1e7时,在我的计算机上通过循环查询值实际上比使用位集快2倍。
当然,通常的时间注意事项可能会有所不同,但如果有人想尝试,这里是我使用的测试代码:
在我的计算机上输出结果为:
1
vector<bool> loop with a size of 10000000 and 10 iterations*n: 11187 ms
bitset<10000000> loop with 10 iterations*n: 22719 ms
101250010
Press any key to continue . . .
位图.cpp
#include "stdafx.h"
#include "BitMap.h"
using namespace std;
// Global var to prevent optimizer from messing things up
volatile size_t ext;
volatile clock_t t1;
volatile clock_t t2;
double delta1;
double delta2;
int main(int argc, _TCHAR* argv[])
{
ext = 1;
printf("%d\n", ext);
vb_t *const vec = new vb_t(bssz);
bs_t *const bits = new bs_t(); // must put large bitset on heap
const int iter = 10;
delta1=0;
delta2=0;
for(int o=0; o<5; ++o) {
t1 = clock();
for(int i=0; i!=5; ++i)
bs_loop(iter, *vec);
t2 = clock();
delta1 += t2-t1;
t1 = clock();
for(int i=0; i!=5; ++i)
bs_loop(iter, *bits);
t2 = clock();
delta2 += t2-t1;
}
delta1 /= CLOCKS_PER_SEC;
delta2 /= CLOCKS_PER_SEC;
delta1 *= 1000;
delta2 *= 1000;
cout << "vector<bool> loop with a size of " << bssz << " and " << iter << " iterations*n: " << delta1 << " ms\n";
cout << "bitset<" << bssz << "> loop with " << iter << " iterations*n: " << delta2 << " ms\n";
printf("%d\n", ext);
delete vec;
delete bits;
return 0;
}
BitMap.h
#pragma once
#include <vector>
#include <bitset>
extern volatile size_t ext;
const size_t bssz = size_t(1e7); // 1e7 ca 10m
using namespace std; // Test code, using here is OK.
typedef vector<bool> vb_t;
typedef bitset<bssz> bs_t;
template<class COLL>
void bs_loop(const int iterations, COLL const& v);
bs_loop.cpp
#include "stdafx.h"
#include "BitMap.h"
template<class COLL>
void bs_loop(const int iterations, COLL const& v)
{
ext = sizeof(COLL);
for(size_t i=0; i!=iterations; ++i) {
++ext;
for(size_t j=0, e=v.size(); j!=e; ++j) {
if(v[j]) {
--ext;
}
else {
++ext;
}
}
}
}
template
void bs_loop(const int iterations, vb_t const& v);
template
void bs_loop(const int iterations, bs_t const& v);
编译器命令行:
/Zi /nologo /W3 /WX- /O2 /Oi /Oy- /D "WIN32" /D "NDEBUG"
/D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /Gm- /EHsc /GS /Gy
/fp:precise /Zc:wchar_t /Zc:forScope /Yu"StdAfx.h" /Fp"Release\BitMap.pch"
/Fa"Release\" /Fo"Release\" /Fd"Release\vc100.pdb" /Gd /analyze-
/errorReport:queue
请注意/O2和缺失/GL(无整个程序优化)。
l-values
但不用于r-values
。由于r-values
是只读的,因此[]
运算符可以返回一个bool
。因此,您可能希望向测试中添加一些位设置,以使其具有良好的覆盖范围。 - Evan Teransizeof(COLL)
和其他 ext
操作,它们对我来说通常没有意义。 - Evan Teranbs[i] = true;
的操作时,operator[]
不能返回一个 bool&
,因为事实上它们并不是以 bool
数组的形式存储的。所以它必须返回一个小对象,其中包括定义了 operator=
的适当位在容器中翻转的内容。而对于像 bool x = bs[i];
这样的情况,情况就不同了,它可以简单地返回一个 bool
。因此,在实现中,bitset
或 vector<bool>
的读取与写入代码非常不同。因此,您需要测试两者才能完整。 - Evan Teranstd::bitset<N>
当前实现大约使用N/CHAR_BIT
字节来存储N个位。因此,它们存储数据的方式与典型的std::vector<bool>
实现方式基本相同(虽然标准对这两者都没有要求)。 - MikeMBvector< bitset<64> > v(100000) //or whatever...
这里有一个有趣的测试,可以将这两个进行比较:
vector<unsigned char> v1(1000000) //8 bits to manage them manually
vector< bitset<8> > v2(1000000) //8 bits managed by bitset
(但所有这些测试都有点棘手,可能只能给我们提供粗略的总体比较。最终要做的是对项目进行分析.)
注意:
用大小为10^7的数据集:
我还包括了对象构造函数和析构函数的开销时间。
下面是简单的测试代码:
#include <iostream>
#include <vector>
#include <bitset>
#include <time.h>
using namespace std;
#define SIZE1 1000000000 //10e9
//#define SIZE2 10000000 //10e7 VS2012 crash at runtime, g++ OK
#define SIZE2 1000000 //10e6
void test1()
{
register bool j;
clock_t t1,t2;
cout.precision(10);
t1=clock();
vector<bool> *v = new vector<bool>(SIZE1);
for(register long int i=0; i<SIZE1;i++)
(*v)[i] = i%2 == 0? true :false;
for(register long int i=0; i<SIZE1;i++)
j=(*v)[i];
delete v;
t2=clock();
cout << "vector speed = " << (t2-t1) / (float) CLOCKS_PER_SEC << " (" << t2 << "," << t1 << ")" << endl;
t1=clock();
bitset<SIZE1> *b = new bitset<SIZE1>();
for(register long int i=0; i<SIZE1;i++)
(*b)[i] = i%2 == 0? true :false;
for(register long int i=0; i<SIZE1;i++)
j=(*b)[i];
delete b;
t2=clock();
cout << "bitset speed = " << (t2-t1) / (float) CLOCKS_PER_SEC << " (" << t2 << "," << t1 << ")" << endl;
}
void test2()
{
register bool j;
clock_t t1,t2;
cout.precision(10);
t1=clock();
vector<bool> v(SIZE2);
for(register int k=0; k<SIZE1/SIZE2; k++)
for(register long int i=0; i<SIZE2;i++)
(v)[i] = i%2 == 0? true :false;
for(register int k=0; k<SIZE1/SIZE2; k++)
for(register long int i=0; i<SIZE2;i++)
j=(v)[i];
t2=clock();
cout << "vector speed = " << (t2-t1) / (float) CLOCKS_PER_SEC << " (" << t2 << "," << t1 << ")" << endl;
cout << "v[1], v[2] " << (int) v[1] << ", "<< (int)v[2] << endl;
t1=clock();
bitset<SIZE2> b;
for(register int k=0; k<SIZE1/SIZE2; k++)
for(register long int i=0; i<SIZE2;i++)
(b)[i] = i%2 == 0? true :false;
for(register int k=0; k<SIZE1/SIZE2; k++)
for(register long int i=0; i<SIZE2;i++)
j=(b)[i];
t2=clock();
cout << "bitset speed = " << (t2-t1) / (float) CLOCKS_PER_SEC << " (" << t2 << "," << t1 << ")" << endl;
cout << "b[1], b[2] " << (int) b[1] << ", "<< (int)b[2] << endl;
}
int main(int argc, char* argv[])
{
test1();
test2();
return 0;
}
VS2012输出:
vector speed = 3.105000019 (3105,0)
bitset speed = 10.44400024 (13551,3107)
vector speed = 3.987999916 (17542,13554)
v[1], v[2] 0, 1
bitset speed = 9.772999763 (27318,17545)
b[1], b[2] 0, 1
mingw/g++输出-O2:
vector speed = 1.519 (1520,1)
bitset speed = 1.647 (3168,1521)
vector speed = 1.383999944 (4554,3170)
v[1], v[2] 0, 1
bitset speed = 1.610000014 (6166,4556)
b[1], b[2] 0, 1
mingw/g++ 输出 -O2 -std=c++11:
vector speed = 1.528 (1529,1)
bitset speed = 1.685 (3215,1530)
vector speed = 1.409999967 (4626,3216)
v[1], v[2] 0, 1
bitset speed = 1.763000011 (6392,4629)
b[1], b[2] 0, 1
g++ 4.8.2 输出 -O2:
vector speed = 1.561391 (1564139,2748)
bitset speed = 1.681818 (3246051,1564233)
vector speed = 1.487877011 (4733975,3246098)
v[1], v[2] 0, 1
bitset speed = 1.685297012 (6419328,4734031)
b[1], b[2] 0, 1
g++ 4.8.2 输出 -O2 -std=c++11:
vector speed = 1.561391 (1564139,2748)
bitset speed = 1.681818 (3246051,1564233)
vector speed = 1.487877011 (4733975,3246098)
v[1], v[2] 0, 1
bitset speed = 1.685297012 (6419328,4734031)
b[1], b[2] 0, 1
结论:
粗略来看,对于这些用例,矢量似乎更快。
我没有运行多个实例并平均结果,但大体上数值总是相同的。
关于VS的注释:我认为它使用了与gcc不同的内存管理机制,在这些用例中生成的代码似乎更慢。
bitset<>
和vector<bool>
中的3亿个元素进行访问/插入的非科学基准测试。编译器是64位Linux机器(Core i7)上的GCC 4.8.2:-O2 -std=c++11
):[estan@pyret bitset_vs_vector]$ ./bitset_vs_vector
bitset<100000> (3 billion accesses/inserts): 132.424 ms
vector<bool>(100000) (3 billion accesses/inserts): 270.577 ms
bitset<1000000> (3 billion accesses/inserts): 67.752 ms
vector<bool>(1000000) (3 billion accesses/inserts): 268.193 ms
bitset<5000000> (3 billion accesses/inserts): 67.426 ms
vector<bool>(5000000) (3 billion accesses/inserts): 267.566 ms
没有优化(编译器标志:-std=c++11
):
[estan@pyret bitset_vs_vector]$ make
g++ -std=c++11 -o bitset_vs_vector *.cpp
[estan@pyret bitset_vs_vector]$ ./bitset_vs_vector
bitset<100000> (3 billion accesses/inserts): 1900.13 ms
vector<bool>(100000) (3 billion accesses/inserts): 1784.76 ms
bitset<1000000> (3 billion accesses/inserts): 1825.09 ms
vector<bool>(1000000) (3 billion accesses/inserts): 1768.03 ms
bitset<5000000> (3 billion accesses/inserts): 1846.73 ms
vector<bool>(5000000) (3 billion accesses/inserts): 1763.48 ms
在这些条件下,当代码被优化时,bitset比vector更快,而当没有优化时,vector实际上略微领先。
话虽如此,如果您的代码时间很关键,最好自己进行基准测试,因为我怀疑这些数字高度依赖于编译器/环境。
基准测试代码:
#include <iostream>
#include <functional>
#include <bitset>
#include <vector>
#include <ctime>
// Performs N access/insert on container.
template<class T>
void access_and_insert(T &container, int N)
{
const std::size_t size = container.size();
for (int i = 0; i < N; ++i) {
bool v = container[i % size];
container[i % size] = true;
}
}
// Measure the time in milliseconds required to call f.
double measure(std::function<void (void)> f)
{
clock_t start = std::clock();
f();
return 1000.0 * (std::clock() - start)/CLOCKS_PER_SEC;
}
int main (void)
{
// Benchmark with 100K elements.
std::bitset<100000> bitset100K;
std::vector<bool> vector100K(100000);
std::cout << "bitset<100000> (3 billion accesses/inserts): ";
std::cout << measure([&]() { access_and_insert(bitset100K, 3E7); }) << " ms " << std::endl;
std::cout << "vector<bool>(100000) (3 billion accesses/inserts): ";
std::cout << measure([&]() { access_and_insert(vector100K, 3E7); }) << " ms" << std::endl;
std::cout << std::endl;
// Benchmark with 1M elements.
std::bitset<1000000> bitset1M;
std::vector<bool> vector1M(1000000);
std::cout << "bitset<1000000> (3 billion accesses/inserts): ";
std::cout << measure([&]() { access_and_insert(bitset1M, 3E7); }) << " ms " << std::endl;
std::cout << "vector<bool>(1000000) (3 billion accesses/inserts): ";
std::cout << measure([&]() { access_and_insert(vector1M, 3E7); }) << " ms" << std::endl;
std::cout << std::endl;
// Benchmark with 5M elements.
std::bitset<5000000> bitset5M;
std::vector<bool> vector5M(5000000);
std::cout << "bitset<5000000> (3 billion accesses/inserts): ";
std::cout << measure([&]() { access_and_insert(bitset5M, 3E7); }) << " ms " << std::endl;
std::cout << "vector<bool>(5000000) (3 billion accesses/inserts): ";
std::cout << measure([&]() { access_and_insert(vector5M, 3E7); }) << " ms" << std::endl;
return 0;
}
向量使用迭代器访问其元素,这些迭代器不能是bool*的简单typedef,这使得它比位集慢,因为位集不提供迭代器。另一个使它快速的因素是它的大小在编译时已知,因此它不会使用new进行分配,而这比堆栈分配更慢。只是一些随机的想法。
vector<bool>
是 vector 模板的一种特化,其实现方式与您想象的可能大不相同。