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
82145int 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