再次关于STL std::bitset
- 它的文档说函数set/reset/test
进行边界检查,operator[]
没有。我的计时实验表明,函数set/test
的执行速度通常比operator[]
快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;
}
如何解释这个?没有边界检查应该为我们节省一些周期,对吗?
Compiler: g++ 4.8.1 (options -g -O4)
VMware VM: Ubuntu 3.11.0-15
Host: MacBook Pro
答案 0 :(得分:3)
当我从时间中删除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;
}
我在http://coliru.stacked-crooked.com/a/cdc832bfcc7e32be与Clang一起得到了这些结果。 (我进行10000次迭代,20次,并测量最短时间,这可以减少计时错误。)
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
此测试的早期版本发现括号略快,但现在我已经提高了时间的准确性,我的时间误差似乎约为3%。在O1 Set
快35-54%,在O2时快13-49%,在O3它快2-34%。除了查看装配输出外,这对我来说似乎很有说服力。
所以这是通过http://assembly.ynh.io/组装(GCC -O
):
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
我无法真正阅读装配,但这些完全相同,因此对这种情况的分析很容易。如果编译器知道它们都在范围内,它会优化范围检查。当我用变量索引替换固定索引时,Set
添加5个操作来检查边界情况。
对于Set
有时更快的原因,operator[]
必须为Set
不必执行的参考代理执行TON工作。 Set
有时较慢的原因是代理很简单,在这种情况下,唯一的区别是Set
必须进行边界检查。另一方面,如果编译器无法证明索引总是在范围内,Set
只需要进行边界检查。所以它取决于周围的代码,很多。您的结果可能会有所不同。
http://en.cppreference.com/w/cpp/utility/bitset/set说:
将位置pos处的位设置为值 如果pos与bitset中的有效位置不对应,则抛出std :: out_of_range。
http://en.cppreference.com/w/cpp/utility/bitset/operator_at说:
访问位置pos处的位。返回std :: bitset :: reference类型的对象,允许修改该值 与test()不同,不会抛出异常:如果pos超出界限,则行为未定义。
和http://en.cppreference.com/w/cpp/utility/bitset/reference说:
std :: bitset类包含std :: bitset :: reference作为可公开访问的嵌套类。此类用作代理对象,以允许用户与位集的各个位进行交互,因为标准C ++类型(如引用和指针)的构建不足以指定单个位。 std :: bitset :: reference的主要用途是提供可以从operator []返回的l值。对通过std :: bitset :: reference发生的bitset的任何读取或写入都可能读取或写入整个基础bitset。
应该很清楚operator[]
实际上比直觉更多。