C++ `std::bitset`有边界检查和无边界检查

C++ `std::bitset`有边界检查和无边界检查,c++,performance,stl,bitset,C++,Performance,Stl,Bitset,关于STLstd::bitset——它的文档说明函数set/reset/test进行边界检查,而操作符[]不进行边界检查。我的计时实验表明,函数set/test的执行速度通常比操作符[]快2-3%。我使用的代码是: typedef unsigned long long U64; const U64 MAX = 800000000ULL; struct Bitmap1 { void insert(U64 N) {this->s[N % MAX] = 1;} bool find(U6

关于STL
std::bitset
——它的文档说明函数
set/reset/test
进行边界检查,而
操作符[]
不进行边界检查。我的计时实验表明,函数
set/test
的执行速度通常比
操作符[]
快2-3%。我使用的代码是:

typedef unsigned long long U64;
const U64 MAX = 800000000ULL;

struct Bitmap1
{
  void insert(U64 N) {this->s[N % MAX] = 1;}
  bool find(U64 N) const {return this->s[N % MAX];}
private:
  std::bitset<MAX> s;  // <---- takes MAX/8 memory (in bytes)
};

struct Bitmap2
{
  void insert(U64 N) {this->s.set(N % MAX);}
  bool find(U64 N) const {return this->s.test(N % MAX);}
private:
  std::bitset<MAX> s;  // <---- takes MAX/8 memory (in bytes)
};

int main()
{
  Bitmap2* s = new Bitmap2();
  // --------------------------- storing
  const size_t t0 = time(0);
  for (unsigned k = 0; k < LOOPS; ++k)
  {
    for (U64 i = 0; i < MAX; ++i) s->insert(i);
  }
  cout << "storing: " << time(0) - t0 << endl;
  // -------------------------------------- search
  const size_t t1 = time(0);
  U64 count = 0;
  for (unsigned k = 0; k < LOOPS; ++k)
  {
    for (U64 i = 0; i < MAX; ++i) if (s->find(i)) ++count;
  }
  cout << "search:  " << time(0) - t1 << endl;
  cout << count << endl;
}

当我从计时中删除
rand
、除法、输出和内存缓存时:

bool bracket_test() {
    std::bitset<MAX> s;
    for(int j=0; j<num_iterations; ++j) {
        for(int i=0; i<MAX; ++i)
            s[i] = !s[MAX-1-i];
    }
    return s[0];
}
bool set_test() {
    std::bitset<MAX> s;
    for(int j=0; j<num_iterations; ++j) {
        for(int i=0; i<MAX; ++i)
            s.set(i, !s.test(MAX-1-i));
    }
    return s.test(0);
}
bool no_test() {
    bool s = false;
    for(int j=0; j<num_iterations; ++j) {
        for(int i=0; i<MAX; ++i)
            s = !s;
    }
    return s;
}
这个测试的早期版本发现括号稍微快一点,但是现在我已经提高了计时的准确性,我的计时误差大约是3%。在O1
Set
时快35-54%,在O2时快13-49%,在O3时快2-34%。除了查看程序集输出之外,这对我来说似乎是非常确定的

下面是通过以下方式进行的组装(在GCC-O上):

std::位集s
s[1000000]=真;
返回s;
0000 4889F8 movq%rdi,%rax
0003 4889FA movq%rdi,%rdx
0006 488D8F00 leaq 100000000(%rdi),%rcx
E1F505
000d 48C70200 movq$0,(%rdx)
000000
0014 4883C208追加8美元,%rdx
0018 4839CA cmpq%rcx,%rdx
001b 75F0 jne.L2
001d 48838848或1125000美元(%rax)
E8010001
0025 C3 ret

std::位集s;
s、 设置(1000000);
返回s;
0026 4889F8 movq%rdi,%rax
0029 4889FA movq%rdi,%rdx
002c 488D8F00 leaq 100000000(%rdi),%rcx
E1F505
0033 48C70200 movq$0,(%rdx)
000000
003a 4883C208追加8美元,%rdx
003e 4839CA cmpq%rcx,%rdx
0041 75F0 jne.L6
0043 48838848或1125000美元(%rax)
E8010001
004b C3 ret
我不能很好地阅读汇编,但它们完全相同,所以分析这个案例很容易。如果编译器知道它们都在范围内,它会优化范围检查。当我用变量索引替换固定索引时,
Set
添加5个操作来检查边界情况

至于
Set
有时更快的原因,是
操作符[]
必须为引用代理做大量
Set
不需要做的工作。
Set
有时速度较慢的原因是代理很少内联,在这种情况下唯一的区别是
Set
必须进行边界检查。另一方面,
Set
只需在编译器无法证明索引始终在范围内时执行边界检查。所以这很大程度上取决于周围的代码。你的结果可能不同

说:

将位置pos处的位设置为值。
如果pos与位集中的有效位置不对应,则抛出std::out_of_range

说:

访问位置处的位。返回允许修改值的std::bitset::reference类型的对象。
与test()不同,它不会抛出异常:如果pos超出边界,则行为未定义

并说:

std::bitset类包括std::bitset::reference作为一个可公开访问的嵌套类。该类被用作代理对象,允许用户与位集中的各个位进行交互,因为标准C++类型(如引用和指针)没有足够精确地构建以指定单个位。std::bitset::reference的主要用途是提供可以从运算符[]返回的l值。通过std::bitset::reference对位集进行的任何读取或写入都可能对整个基础位集进行读取或写入


应该清楚的是,
operator[]
实际上比直观的要多得多。

这可能是基准错误。请给出完整的例子。你在做什么具体的实验?您能告诉我们您尝试了什么、使用了什么编译器和编译器标志以及结果吗?@AlekseyYakovlev:
Bitset::operator[]
必须创建并使用代理<代码>比特集::Set不需要做这样的事情。我认为
Set
实际上要快得多,但由于缓存分配100MB的数据,差异就消失了。使用位集非常便宜,以至于您的测试完全由rand甚至%运算符控制,这实际上是一个非常慢的除法。这意味着您的基准测试对编译器更改无关代码的内容非常敏感。您没有以有效的方式进行基准测试。首先修复基准。@MooingDuck-我指的是二进制大小爆炸性增长导致的二阶效应,这里的公式是这样表述的,底线是-我们不应该期望
操作符[]
总是比
设置/测试
快,对吗?如果是的话,这应该进入一些广为人知的关于
std::bitset
I的文档中think@AlekseyYakovlev<代码>操作符[]<代码> VS <代码> SET的相对性能是,就像C++中的所有其他一样,实现了定义。@ MooingDuck,嗯,我不同意。如果程序逻辑允许STL用户避免边界检查,那么在任何优化级别上都不应该对其征税。@AlekseyYakovlev:有趣的是,GCC似乎没有正确地优化
运算符[]
,但clang确实如此。我想知道MSVC做什么
operator[]
旨在避免边界检查,bug GCC似乎无法使其可行。@AlekseyYakovlev:我做了更精确的计时测试,并考虑了一些差异。这一次,在所有优化级别上,
Set
确实更快。
bool bracket_test() {
    std::bitset<MAX> s;
    for(int j=0; j<num_iterations; ++j) {
        for(int i=0; i<MAX; ++i)
            s[i] = !s[MAX-1-i];
    }
    return s[0];
}
bool set_test() {
    std::bitset<MAX> s;
    for(int j=0; j<num_iterations; ++j) {
        for(int i=0; i<MAX; ++i)
            s.set(i, !s.test(MAX-1-i));
    }
    return s.test(0);
}
bool no_test() {
    bool s = false;
    for(int j=0; j<num_iterations; ++j) {
        for(int i=0; i<MAX; ++i)
            s = !s;
    }
    return s;
}
clang++ -std=c++11  -O0 -Wall -Wextra -pedantic -pthread main.cpp  && ./a.out
bracket_test took 178663845 ticks to find result 1
set_test     took 117336632 ticks to find result 1
no_test      took 9214297 ticks to find result 0
clang++ -std=c++11  -O1 -Wall -Wextra -pedantic -pthread main.cpp  && ./a.out
bracket_test took 798184780 ticks to find result 1
set_test     took 565999680 ticks to find result 1
no_test      took 41693575 ticks to find result 0
clang++ -std=c++11  -O2 -Wall -Wextra -pedantic -pthread main.cpp  && ./a.out
bracket_test took 81240369 ticks to find result 1
set_test     took 72172912 ticks to find result 1
no_test      took 41907685 ticks to find result 0
clang++ -std=c++11  -O3 -Wall -Wextra -pedantic -pthread main.cpp  && ./a.out
bracket_test took 77688054 ticks to find result 1
set_test     took 72433185 ticks to find result 1
no_test      took 41433010 ticks to find result 0
std::bitset<MAX> s
s[1000000] = true;
return s;

0000 4889F8         movq    %rdi, %rax
0003 4889FA         movq    %rdi, %rdx
0006 488D8F00       leaq    100000000(%rdi), %rcx
     E1F505
000d 48C70200       movq    $0, (%rdx)
     000000
0014 4883C208       addq    $8, %rdx
0018 4839CA         cmpq    %rcx, %rdx
001b 75F0           jne .L2
001d 48838848       orq $1, 125000(%rax)
     E8010001 
0025 C3             ret
std::bitset<MAX> s;
s.set(1000000);
return s;

0026 4889F8         movq    %rdi, %rax
0029 4889FA         movq    %rdi, %rdx
002c 488D8F00       leaq    100000000(%rdi), %rcx
     E1F505
0033 48C70200       movq    $0, (%rdx)
     000000
003a 4883C208       addq    $8, %rdx
003e 4839CA         cmpq    %rcx, %rdx
0041 75F0           jne .L6
0043 48838848       orq $1, 125000(%rax)
     E8010001 
004b C3             ret