使用的布尔值向量是否比动态位集慢?
我刚刚听说过boost的动态bitset,我想知道它是否值得 麻烦。我可以只使用布尔值向量吗?
答案 0 :(得分:24)
这里有很多,取决于您使用的布尔值的数量。
bitset和vector<bool>
通常都使用压缩表示,其中布尔值仅存储为一位。
一方面,这会以位操作的形式强加一些开销来访问单个值。
另一方面,这也意味着你的更多布尔人将适合你的缓存。
如果你使用了很多布尔(例如,实施了一个Eratosthenes的筛子),那么在缓存中安装更多的布林将几乎总是获得净收益。内存使用的减少将比比特操作失去更多。
针对std::vector<bool>
的大多数论据都回到了它不是标准容器的事实(即,它不符合容器的要求)。国际海事组织,这主要是一个期望的问题 - 因为它说vector
,许多人都认为它是一个容器(其他类型的载体),并且它们经常对vector<bool>
这一事实做出消极反应。不是容器。
如果你以一种真正需要它为容器的方式使用向量,那么你可能想要使用其他组合 - deque<bool>
或vector<char>
可以正常工作。 想想然后才这样做 - 有很多(糟糕的,IMO)建议,vector<bool>
一般应该避免,很少或根本没有解释为什么应该避免它,或在什么情况下它会对你产生真正的影响。
是的,有些情况会有更好的效果。如果您处于其中一种情况,使用其他东西显然是个好主意。但是,请确保您首先处于其中一种情况。任何告诉你(例如)“Herb说你应该使用vector<char>
”但没有对所涉及的权衡做出大量解释的人都不应该被信任。
让我们举一个真实的例子。由于评论中提到过,让我们考虑一下Eratosthenes的Sieve:
#include <vector>
#include <iostream>
#include <iterator>
#include <chrono>
unsigned long primes = 0;
template <class bool_t>
unsigned long sieve(unsigned max) {
std::vector<bool_t> sieve(max, false);
sieve[0] = sieve[1] = true;
for (int i = 2; i < max; i++) {
if (!sieve[i]) {
++primes;
for (int temp = 2 * i; temp < max; temp += i)
sieve[temp] = true;
}
}
return primes;
}
// Warning: auto return type will fail with older compilers
// Fine with g++ 5.1 and VC++ 2015 though.
//
template <class F>
auto timer(F f, int max) {
auto start = std::chrono::high_resolution_clock::now();
primes += f(max);
auto stop = std::chrono::high_resolution_clock::now();
return stop - start;
}
int main() {
using namespace std::chrono;
unsigned number = 100000000;
auto using_bool = timer(sieve<bool>, number);
auto using_char = timer(sieve<char>, number);
std::cout << "ignore: " << primes << "\n";
std::cout << "Time using bool: " << duration_cast<milliseconds>(using_bool).count() << "\n";
std::cout << "Time using char: " << duration_cast<milliseconds>(using_char).count() << "\n";
}
我们使用了一个足够大的数组,我们可以预期它的很大一部分会占用主内存。我也有点痛苦,以确保在一次调用和另一次调用之间发生变化的仅事情是使用vector<char>
与vector<bool>
。这是一些结果。首先使用VC ++ 2015:
ignore: 34568730
Time using bool: 2623
Time using char: 3108
...然后使用g ++ 5.1的时间:
ignore: 34568730
Time using bool: 2359
Time using char: 3116
显然,vector<bool>
在两种情况下均胜出 - 使用VC ++约为15%,使用gcc则超过30%。另请注意,在这种情况下,我选择的尺寸显示vector<char>
非常有利。例如,如果我将number
从100000000
缩减为10000000
,则时差会变得多更大:
ignore: 3987474
Time using bool: 72
Time using char: 249
虽然我还没有做很多工作来确认,但我想在这种情况下,使用vector<bool>
的版本节省了足够的空间,使得数组完全适合缓存,而{{{ 1}}足以溢出缓存,并涉及大量主内存访问。
答案 1 :(得分:11)
您通常应该避免使用std::vector<bool>
,因为它不是标准容器。它是一个打包版本,所以它打破了vector
通常给出的一些有价值的保证。一个有效的替代方案是使用std::vector<char>
,这是Herb Sutter推荐的。
更新:
正如已经指出的那样,vector<bool>
可以用来产生良好的效果,因为打包表示可以改善大型数据集的局部性。根据具体情况,它可能是最快的选择。但是,我仍然不会默认推荐它,因为它打破了std::vector
建立的许多承诺,并且打包是速度/内存权衡,可能对速度和内存都有益。
如果您选择使用它,我会根据您的应用程序对vector<char>
进行测量后再这样做。即便如此,我还是建议使用typedef
通过名称来引用它,而这个名称似乎并不能保证它不能保留。
答案 2 :(得分:1)
似乎无法更改动态位集的大小: “dynamic_bitset类与std :: bitset类几乎相同。不同之处在于dynamic_bitset的大小(位数)是在构造dynamic_bitset对象时的运行时指定的,而std的大小则是:: bitset是在编译时通过整数模板参数指定的。“ (来自http://www.boost.org/doc/libs/1_36_0/libs/dynamic_bitset/dynamic_bitset.html) 因此,它应该稍微快一点,因为它的开销略小于向量,但是你无法插入元素。
答案 3 :(得分:1)
更新:我刚刚意识到OP询问的是vector<bool>
vs bitset
,而我的回答并没有回答这个问题,但我想我应该离开它,如果你搜索 c ++ vector bool slow ,你最终会在这里。
vector<bool>
速度非常慢。至少在我的Arch Linux系统上(你可能会得到更好的实现或者什么......但我真的很惊讶)。如果有人有任何建议为什么这么慢,我全都耳朵! (对不起,这是一个比较专业的部分。)
我已经写了两个SOE的实现,并且接近金属&#39; C实现速度提高了10倍。 sievec.c
是C实现,sievestl.cpp
是C ++实现。我只使用make
编译(仅限隐式规则,没有makefile):C版本的结果 1.4秒,而C ++版本的结果 12秒 STL版本:
sievecmp % make -B sievec && time ./sievec 27
cc sievec.c -o sievec
aa 1056282
./sievec 27 1.44s user 0.01s system 100% cpu 1.455 total
和
sievecmp % make -B sievestl && time ./sievestl 27
g++ sievestl.cpp -o sievestl
1056282./sievestl 27 12.12s user 0.01s system 100% cpu 12.114 total
sievec.c
如下:
#include <stdio.h>
#include <stdlib.h>
typedef unsigned long prime_t;
typedef unsigned long word_t;
#define LOG_WORD_SIZE 6
#define INDEX(i) ((i)>>(LOG_WORD_SIZE))
#define MASK(i) ((word_t)(1) << ((i)&(((word_t)(1)<<LOG_WORD_SIZE)-1)))
#define GET(p,i) (p[INDEX(i)]&MASK(i))
#define SET(p,i) (p[INDEX(i)]|=MASK(i))
#define RESET(p,i) (p[INDEX(i)]&=~MASK(i))
#define p2i(p) ((p)>>1) // (((p-2)>>1))
#define i2p(i) (((i)<<1)+1) // ((i)*2+3)
unsigned long find_next_zero(unsigned long from,
unsigned long *v,
size_t N){
size_t i;
for (i = from+1; i < N; i++) {
if(GET(v,i)==0) return i;
}
return -1;
}
int main(int argc, char *argv[])
{
size_t N = atoi(argv[1]);
N = 1lu<<N;
// printf("%u\n",N);
unsigned long *v = malloc(N/8);
for(size_t i = 0; i < N/64; i++) v[i]=0;
unsigned long p = 3;
unsigned long pp = p2i(p * p);
while( pp <= N){
for(unsigned long q = pp; q < N; q += p ){
SET(v,q);
}
p = p2i(p);
p = find_next_zero(p,v,N);
p = i2p(p);
pp = p2i(p * p);
}
unsigned long sum = 0;
for(unsigned long i = 0; i+2 < N; i++)
if(GET(v,i)==0 && GET(v,i+1)==0) {
unsigned long p = i2p(i);
// cout << p << ", " << p+2 << endl;
sum++;
}
printf("aa %lu\n",sum);
// free(v);
return 0;
}
sievestl.cpp
如下:
#include <iostream>
#include <vector>
#include <sstream>
using namespace std;
inline unsigned long i2p(unsigned long i){return (i<<1)+1; }
inline unsigned long p2i(unsigned long p){return (p>>1); }
inline unsigned long find_next_zero(unsigned long from, vector<bool> v){
size_t N = v.size();
for (size_t i = from+1; i < N; i++) {
if(v[i]==0) return i;
}
return -1;
}
int main(int argc, char *argv[])
{
stringstream ss;
ss << argv[1];
size_t N;
ss >> N;
N = 1lu<<N;
// cout << N << endl;
vector<bool> v(N);
unsigned long p = 3;
unsigned long pp = p2i(p * p);
while( pp <= N){
for(unsigned long q = pp; q < N; q += p ){
v[q] = 1;
}
p = p2i(p);
p = find_next_zero(p,v);
p = i2p(p);
pp = p2i(p * p);
}
unsigned sum = 0;
for(unsigned long i = 0; i+2 < N; i++)
if(v[i]==0 and v[i+1]==0) {
unsigned long p = i2p(i);
// cout << p << ", " << p+2 << endl;
sum++;
}
cout << sum;
return 0;
}