使用布尔值的向量比动态位集慢吗?

时间:2013-05-24 15:29:42

标签: c++ vector

使用的布尔值向量是否比动态位集慢?

我刚刚听说过boost的动态bitset,我想知道它是否值得 麻烦。我可以只使用布尔值向量吗?

4 个答案:

答案 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>非常有利。例如,如果我将number100000000缩减为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推荐的。

您可以在GotW on the subject.

中详细了解相关信息

更新:

正如已经指出的那样,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;
}