bitset和bool哪个更快

前言:

牛客2021多校Alice and Bob ,在这题中5000 * 5000 * 5000的复杂度,用bitset 140ms,bool 493ms。bitset快到起飞。
AcWing348–最优比率生成树,这题用bitset会TLE,用bool反而AC。

讨论:

如果一直连续访问数组中的元素,bool明显快于bitset。
bool : 26ms
bitset : 108ms

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6 + 5, mod = 1e9 + 7;
bitset<N> f;
signed main()
{
   
   
    srand((unsigned)time(0));
    
你问得非常好,而且非常关键! > **“不是说 `vector<bool>` 读取更慢吗?”** ✅ 是的,**通常情况下确实如此**。 但为什么前面我们又说:“用 `vector<bool>` 或 `bitset` 优化后,埃式筛可能比欧拉筛还”? 这看似矛盾,实则揭示了一个深刻道理: > **单次访问速度 ≠ 整体性能** 下面我们彻底解释这个现象: 为什么 `vector<bool>` 单次访问慢,但在筛法中整体表现却可能很好? --- ## 🔍 1. 为什么 `vector<bool>` 读取更慢? ### ✅ 标准规定:`vector<bool>` 是特化的位压缩容器 它不存储真正的 `bool`(每个占 1 字节),而是: - 每个布尔值只占 **1 bit** - 多个 bool 存在一个整型单元中(如 `uint32_t`) ### ⚠️ 代价:访问需要“位运算” 例如要获取 `vec[i]`: ```cpp // 实际操作(简化) size_t word_index = i / 32; size_t bit_offset = i % 32; return (data[word_index] >> bit_offset) & 1; ``` 👉 所以每次访问都要: - 计算下标 - 移位 + 与操作 - 返回的是一个临时的“代理对象”(`std::vector<bool>::reference`) ❌ 不能返回 `bool&`,也不能取地址 ❌ 不符合 STL 容器的一般行为(是个设计争议) ✅ 好处:内存节省 8 倍! | 容器 | $ n=10^7 $ 内存占用 | |------|------------------| | `vector<char>` | ~10 MB | | `vector<bool>` | ~1.25 MB | | `bitset<10000000>` | ~1.25 MB | --- ## 🧪 2. 既然单次访问慢,为何整体更快? 因为程序性能不仅取决于“每次访问多”,还取决于: | 因素 | 影响 | |------|------| | ✅ 缓存命中率 | 数据在 L1/L2 缓存中 ⇒ 极 | | ❌ 缓存未命中 | 数据在主存中 ⇒ 极慢(百倍延迟) | | ✅ 内存带宽 | 小数据块传输更快 | | ✅ 预取效率 | 规律访问可被 CPU 预测 | 而 `vector<bool>` 的优势正是这些! --- ## 📈 实验对比说明 假设我们要对数组进行大量写操作(如埃式筛中的标记合数): | 方案 | 单次写入速度 | 总体运行时间 | 原因 | |------|---------------|----------------|-------| | `vector<char>` | (直接赋值) | 较慢 | 占用大内存 → 缓存频繁换入换出 | | `vector<bool>` | 慢(需位运算) | **反而更快** | 数据紧凑 → 几乎全在缓存中 | 📌 关键结论: > **缓存未命中的代价远高于几次位运算!** 现代 CPU: - L1 缓存访问:约 1 ns - 主存访问:约 100 ns - 一次位运算:几条指令,不到 1 ns 所以宁愿多做几次位运算,也不愿从内存里重新加载一页数据。 --- ## 💡 类比理解 想象你在图书馆找书: - `vector<char>`:书架巨大,你要走遍整个馆才能找到所有书(缓存不命中) - `vector<bool>`:书架很小,所有书都在眼前(全在缓存中) 虽然每本书贴了密码标签(需要解码——类比位运算),但你不用走路! ➡️ 显然后者更快。 --- ## ✅ 在什么场景下 `vector<bool>` 真的慢? 当出现以下情况时,`vector<bool>` 会明显变慢: ### ❌ 场景 1:随机频繁访问单个元素 ```cpp for (int i = 0; i < n; ++i) { if (rand() % 2) vec[rand() % n] = !vec[rand() % n]; } ``` → 频繁解包 proxy 对象,无法优化 ### ❌ 场景 2:编译器无法优化循环 → 无法向量化或展开 ### ✅ 但在筛法中: - 循环结构清晰 - 访问模式规律(等差数列) - 编译器可以部分优化(尤其是 `bitset`) - 更重要的是:**整个数组能放进缓存** 所以综合表现优异 --- ## 🛠️ 补充:`bitset` vs `vector<bool>` | 特性 | `bitset<N>` | `vector<bool>` | |------|-------------|----------------| | 大小是否固定 | 是(编译期确定) | 否(运行期可变) | | 是否支持位运算 | ✅ 支持 `.flip()`, `&=`, `\|=` 等 | ❌ 不支持 | | 是否缓存友好 | ✅ 极好(连续+紧凑) | ✅ 好 | | 单次访问速度 | 略(常量优化) | 略慢 | | 推荐用于筛法? | ✅ 强烈推荐 | ✅ 推荐(如果大小不定) | 👉 所以对于已知上限的筛法(如 $ n \leq 10^7 $),优先使用: ```cpp bitset<10000001> is_prime; ``` --- ## ✅ 正确总结回答你的问题 > “不是说 `vector<bool>` 读取更慢吗?” ✅ **是的,单次读取/写入确实更慢**,因为它涉及位运算代理对象。 ❌ 但它不一定导致“程序整体更慢”。 ✅ 在像埃拉托斯特尼筛这样的算法中,由于: 1. **内存占用减少 8 倍** 2. **整个数组更容易装入 CPU 缓存** 3. **批量操作具有高度局部性规律性** 4. **编译器能对循环做一定优化** → **缓存带来的收益远远超过位运算的开销** 因此,在实践中,使用 `vector<bool>` 或更好的 `bitset`,可以让埃式筛的实际运行速度**超过理论更快的欧拉筛**。 --- ###
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值