Skip to content

Commit 1499893

Browse files
committed
update README, using a different pitch
1 parent acbcb5b commit 1499893

1 file changed

Lines changed: 187 additions & 62 deletions

File tree

README.md

Lines changed: 187 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,47 @@
11
# bitvector
22

3-
A compact library focused on lightning-fast bit operations. This project delivers
4-
hardware-friendly algorithms that outpace the STL `bitset`, giving your
5-
applications measurable speed gains alongside an intuitive API.
6-
7-
## Running the benchmarks without bounds checking
8-
9-
Bounds checking is enabled by default. To benchmark without checks, configure and build with (this defines `BITVECTOR_NO_BOUND_CHECK`). The build system now detects whether the compiler and host CPU support AVX2 or other native instructions and enables them when possible:
10-
11-
```bash
12-
cmake -S . -B build -DBITVECTOR_NO_BOUND_CHECK=ON -DCMAKE_BUILD_TYPE=Release
13-
cmake --build build --config Release
14-
./build/bitvector_benchmark
15-
```
16-
17-
## GitHub Actions Benchmark Results
18-
19-
The following table shows benchmark times from the latest CI run. Each test was executed with `1<<20` bits.
20-
21-
| My Function | Time (ns) | STL Function | Time (ns) | Speedup |
22-
|-------------|-----------|--------------|-----------|---------|
3+
`bitvector` is a compact C++17 bit-vector prototype for high-throughput
4+
packed-bit workloads. It targets code that wants the memory density of
5+
`std::vector<bool>` while reducing overhead in common set, access, append, and
6+
scan paths.
7+
8+
This repository is intentionally small: one core header, focused unit tests,
9+
Google Benchmark coverage, CMake configuration, and CI workflows. It is best
10+
read as a performance-engineering proof point rather than a finished production
11+
library.
12+
13+
## Why It Matters
14+
15+
Packed bitmaps show up anywhere large boolean state needs to move quickly:
16+
search and filtering, indexes, compression, graph traversal, allocation maps,
17+
schedulers, feature flags, and analytics engines. In these systems, memory
18+
traffic and per-bit branching can dominate runtime.
19+
20+
For an investor, this project demonstrates that focused low-level optimization
21+
can unlock measurable gains in infrastructure components where small per-bit
22+
improvements compound at scale. For an interviewer, it shows hands-on systems
23+
work: custom storage, proxy references, iterators, bounds-check tradeoffs,
24+
SIMD-aware operations, benchmark design, and CI validation.
25+
26+
## Proof Points
27+
28+
- Header-first C++17 implementation in `bitvector.hpp`.
29+
- Benchmarked against `std::vector<bool>` with Google Benchmark.
30+
- Unit-tested with GoogleTest and exercised in GitHub Actions.
31+
- Uses packed word storage, fast word indexing, BMI/TZCNT scanning, and
32+
SIMD-aware specialized setters.
33+
- CMake detects native compiler/CPU flags where possible and can enable AVX2 or
34+
optional AVX-512 paths.
35+
36+
## Benchmark Snapshot
37+
38+
The table below is a CI snapshot recorded for `1 << 20` bits. It is useful as a
39+
directional proof point, not a universal performance guarantee. Hardware,
40+
compiler, flags, bit density, access pattern, and safety settings can all change
41+
the result, so target workloads should rerun the benchmark locally.
42+
43+
| BitVector Benchmark | Time (ns) | `std::vector<bool>` Benchmark | Time (ns) | Speedup |
44+
|---------------------|-----------|-------------------------------|-----------|---------|
2345
| BM_Bowen_Set | 1826751 | BM_Std_Set | 2975545 | 1.63x |
2446
| BM_Bowen_PushBack | 1998142 | BM_Std_PushBack | 2990558 | 1.50x |
2547
| BM_Bowen_Access | 984200 | BM_Std_Access | 2257978 | 2.29x |
@@ -29,61 +51,164 @@ The following table shows benchmark times from the latest CI run. Each test was
2951
| BM_Bowen_QSetBitTrue6V2 | 2248553 | BM_Std_QSetBitTrue6 | 3133552 | 1.39x |
3052
| BM_Bowen_IncrementUntilZero | 34849 | BM_Std_IncrementUntilZero | 1941798 | 55.72x |
3153

32-
`BM_Bowen_IncrementUntilZero` is the fastest benchmark, showing a substantial improvement over the standard approach.
54+
The largest speedup comes from `incrementUntilZero`, a specialized scan that
55+
skips whole machine words by counting trailing zeros in the inverted word.
3356

34-
## Using `incrementUntilZero`
57+
Benchmark environment for the snapshot above:
3558

36-
`incrementUntilZero` scans the bit vector starting at the given position until it reaches the first zero bit. The position is updated in place:
37-
38-
```cpp
39-
BitVector<> bv(1024, true);
40-
bv.set_bit(1023, false); // ensure there is a zero bit
41-
size_t pos = 0;
42-
bv.incrementUntilZero(pos);
43-
// `pos` now holds the index of the first zero bit
59+
```text
60+
Architecture: x86_64
61+
CPU(s): 5
62+
Model name: Intel(R) Xeon(R) Platinum 8272CL CPU @ 2.60GHz
4463
```
4564

65+
## Design Highlights
66+
67+
- **Packed storage:** bits are stored in `unsigned long` words, with word and
68+
bit offsets computed through shifts and masks.
69+
- **Fast indexing:** `WORD_SHIFT` is computed at compile time and checked with a
70+
`static_assert` so word indexing can use shifts instead of division.
71+
- **Proxy references:** mutable `operator[]` returns a bit reference object, so
72+
callers can write `bv[i] = true` while the vector remains packed.
73+
- **Specialized scan:** `incrementUntilZero(size_t& pos)` advances through runs
74+
of set bits and uses `_tzcnt_u64` to skip full words efficiently.
75+
- **SIMD-aware setters:** functions such as `set_bit_true_6` and
76+
`qset_bit_true_6_v2` explore faster paths for structured write patterns.
77+
- **Allocator flexibility:** the implementation is allocator-templated and
78+
includes an aligned allocator option backed by `_mm_malloc`.
79+
- **Build-time tuning:** CMake enables BMI support, attempts native/AVX2
80+
compiler flags, includes SIMDe, and leaves AVX-512 behind an explicit option.
81+
82+
## Engineering Tradeoffs
83+
84+
This project is optimized for experiments and benchmarks, so it exposes both
85+
safe and unsafe paths:
86+
87+
- The current CMake default sets `BITVECTOR_NO_BOUND_CHECK=ON`, which compiles
88+
out bounds checks for performance-oriented builds.
89+
- Set `-DBITVECTOR_NO_BOUND_CHECK=OFF` when validating caller behavior or
90+
debugging out-of-range access.
91+
- `set_bit_true_unsafe` and SIMD-style setters assume the caller has already
92+
proven valid positions and sizes.
93+
- `data()` exposes raw storage for low-level integration, but that also means
94+
callers can bypass invariants.
95+
- The current implementation is x86/x64-oriented because it directly includes
96+
native intrinsic headers; ARM support is a clear portability follow-up.
97+
- The repository does not currently include packaging metadata or a license
98+
file, so external adoption should start by resolving those basics.
99+
100+
## Quick Start
101+
102+
Requirements:
103+
104+
- CMake 3.21 or newer.
105+
- A C++17 compiler.
106+
- An x86 or x64 target for the current prototype. The header includes native
107+
x86 intrinsic headers, so ARM builds need portability work before they can
108+
compile cleanly.
109+
- Network access on first configure, because CMake fetches GoogleTest, SIMDe,
110+
and Google Benchmark.
111+
112+
If you are using CMake 4.x, the pinned GoogleTest release may require adding
113+
`-DCMAKE_POLICY_VERSION_MINIMUM=3.5` to the configure command until that
114+
dependency is upgraded.
115+
116+
Build, test, and run benchmarks:
46117

47-
## CPU Info
118+
```bash
119+
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
120+
cmake --build build --config Release
121+
ctest --test-dir build --output-on-failure
122+
./build/bitvector_benchmark
123+
```
48124

49-
The benchmarks above were run on the following machine:
125+
Run a debug-oriented build with runtime bounds checks enabled:
50126

127+
```bash
128+
cmake -S . -B build-safe -DBITVECTOR_NO_BOUND_CHECK=OFF -DCMAKE_BUILD_TYPE=Debug
129+
cmake --build build-safe --config Debug
130+
ctest --test-dir build-safe --output-on-failure
51131
```
52-
Architecture: x86_64
53-
CPU(s): 5
54-
Model name: Intel(R) Xeon(R) Platinum 8272CL CPU @ 2.60GHz
132+
133+
Run the shorter benchmark configuration used by CI:
134+
135+
```bash
136+
./build/bitvector_benchmark --benchmark_min_time=0.01s
55137
```
56-
## API Reference
57-
58-
Below is a quick guide to the main `bowen::BitVector` functions:
59-
60-
- **`BitVector()`** – Construct an empty vector.
61-
- **`BitVector(size_t n, bool value = false)`** – Create a vector with `n` bits initialized to `value`.
62-
- **`operator[]`** – Access or modify a bit by index.
63-
- **`set_bit(size_t pos, bool value)`** – Set the bit at `pos` to `value`.
64-
- **`set_bit_true_unsafe(size_t pos)`** – Like `set_bit(pos, true)` but without bounds checking.
65-
- **`qset_bit_true_6_v2(size_t pos, size_t stride, size_t size)`** – Use SIMD to quickly set bits in a stride pattern.
66-
- **`set_bit_true_6(size_t pos, size_t stride)`** – Set six bits starting from `pos` with the given `stride`.
67-
- **`incrementUntilZero(size_t& pos)`** – Advance `pos` to the first zero bit from its current value.
68-
- **`push_back(bool value)`** – Append a bit to the end of the vector.
69-
- **`reserve(size_t new_capacity)`** – Ensure capacity for at least `new_capacity` bits.
70-
- **`assign(size_t n, bool value)`** – Resize the vector to `n` bits and set each bit to `value`.
71-
- **`data()`** – Pointer to the underlying storage.
72-
- **`size()`** – Number of bits currently stored.
73-
- **`empty()`** – Check whether the vector is empty.
74-
- **`begin()` / `end()`** – Iterators for range-based loops.
75-
76-
Example:
138+
139+
## Usage Example
77140

78141
```cpp
79142
#include "bitvector.hpp"
80-
using bowen::BitVector;
143+
#include <cstddef>
81144

82145
int main() {
83-
BitVector<> bv(8); // all bits start as 0
84-
bv.set_bit(3, true); // set the fourth bit
85-
bv.push_back(true); // append one bit
86-
size_t pos = 0;
87-
bv.incrementUntilZero(pos); // pos now points to the first zero bit
146+
bowen::BitVector<> bits(1024, false);
147+
148+
bits.set_bit(3, true);
149+
bits.push_back(true);
150+
151+
bowen::BitVector<> dense(1024, true);
152+
dense.set_bit(511, false);
153+
154+
std::size_t pos = 0;
155+
dense.incrementUntilZero(pos);
156+
// pos now holds 511, the first zero bit in the dense vector.
88157
}
89158
```
159+
160+
## API Surface
161+
162+
Core `bowen::BitVector` operations:
163+
164+
- `BitVector()` creates an empty vector.
165+
- `BitVector(size_t n, bool value = false)` creates `n` initialized bits.
166+
- `operator[]` reads or writes a bit by index.
167+
- `set_bit(size_t pos, bool value)` sets a specific bit.
168+
- `set_bit_true_unsafe(size_t pos)` sets a bit without bounds checking.
169+
- `set_bit_true_6(size_t pos, size_t stride)` sets six strided bits.
170+
- `qset_bit_true_6_v2(size_t pos, size_t stride, size_t size)` explores a
171+
SIMD-style path for repeated strided writes.
172+
- `incrementUntilZero(size_t& pos)` advances `pos` to the next zero bit.
173+
- `push_back(bool value)` appends one bit.
174+
- `reserve(size_t new_capacity)` reserves capacity measured in bits.
175+
- `assign(size_t n, bool value)` resizes and fills the vector.
176+
- `data()` returns the underlying word storage.
177+
- `size()` returns the number of logical bits.
178+
- `empty()` reports whether the vector has no bits.
179+
- `begin()` and `end()` provide iterator access for traversal.
180+
181+
## Validation And CI
182+
183+
The repository includes two GitHub Actions workflows:
184+
185+
- `unit_tests.yml` configures a release build, builds the project, and runs
186+
GoogleTest through `ctest`.
187+
- `performance.yml` builds the benchmark target, runs Google Benchmark, and
188+
dumps assembly for selected benchmark functions.
189+
190+
Local tests cover construction, `push_back`, copy and assignment, resizing,
191+
iterator traversal, bounds-check behavior when enabled, and
192+
`incrementUntilZero`.
193+
194+
## Repository Map
195+
196+
- `bitvector.hpp` contains the core implementation.
197+
- `bitvector_test.cpp` contains GoogleTest unit coverage.
198+
- `bitvector_benchmark.cpp` contains Google Benchmark comparisons against
199+
`std::vector<bool>`.
200+
- `CMakeLists.txt` defines build options, dependencies, tests, and benchmarks.
201+
- `.github/workflows/` contains CI validation and benchmark workflows.
202+
- `scripts/dump_benchmark_asm.sh` helps inspect generated assembly for selected
203+
benchmark functions.
204+
205+
## Roadmap
206+
207+
- Add a license and packaging metadata before external distribution.
208+
- Stabilize API naming and document safe versus unsafe entry points.
209+
- Broaden portability and benchmarks across CPUs, compilers, operating systems,
210+
and real bitmap-heavy workloads.
211+
- Expand STL compatibility, including const traversal and clearer iterator
212+
semantics.
213+
- Add fuzz or property-based tests for randomized bit patterns and boundary
214+
conditions.

0 commit comments

Comments
 (0)