Skip to content

Commit f55d465

Browse files
committed
docs: Add Python requirements file for data visualization dependencies
1 parent a478ea8 commit f55d465

2 files changed

Lines changed: 34 additions & 69 deletions

File tree

README.md

Lines changed: 32 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,20 @@ A Rust library that provides space-efficient, in-memory representations for inte
1111

1212
The library is designed to reduce the memory footprint of standard [`std::vec::Vec`] collections of integers while retaining performant access patterns.
1313

14-
## Core Structures: `FixedVec` and `IntVec`
14+
## Core Structures: [`FixedVec`] and [`IntVec`]
1515

1616
The library provides two distinct vector types, each based on a different encoding principle. Choosing the right one depends on your specific use case, performance requirements, and data characteristics.
1717

18-
### `FixedVec`: Fixed-Width Encoding
18+
### [`FixedVec`]: Fixed-Width Encoding
1919

2020
Implements a vector where every integer occupies the same, predetermined number of bits.
2121

2222
* **Key Features**:
23-
* **O(1) Random Access**: The memory location of any element is determined by a direct bit-offset calculation, resulting in minimal-overhead access. With low bit widths (e.g., 8, 16, 32), `FixedVec` can be 2-3x faster than `std::vec::Vec` for random access.
23+
* **O(1) Random Access**: The memory location of any element is determined by a direct bit-offset calculation, resulting in minimal-overhead access. With low bit widths (e.g., 8, 16, 32), [`FixedVec`] can be 2-3x faster than [`std::vec::Vec`] for random access.
2424
* **Mutability**: Supports in-place modifications after creation through an API similar to [`std::vec::Vec`] (e.g., `push`, `set`, `pop`).
2525
* **Atomic Operations**: Provides [`AtomicFixedVec`], a thread-safe variant that supports atomic read-modify-write operations for concurrent environments.
2626

27-
* **Use Cases**:
28-
* Data with a uniform or near-uniform distribution where all values fit within a known bit width.
29-
* Applications where low-latency random access is the primary performance requirement.
30-
* Scenarios requiring in-place vector modification or concurrent atomic updates.
31-
32-
33-
### `IntVec`: Variable-Width Encoding
27+
### [`IntVec`]: Variable-Width Encoding
3428

3529
Implements a vector using variable-length instantaneous codes (e.g., Gamma, Delta, Rice, Zeta) to represent each integer.
3630

@@ -39,32 +33,17 @@ Implements a vector using variable-length instantaneous codes (e.g., Gamma, Delt
3933
* **Automatic Codec Selection**: Can analyze the data to select the most effective compression codec automatically.
4034
* **Amortized O(1) Random Access**: Enables fast random access by sampling the bit positions of elements at a configurable interval (`k`).
4135

42-
* **Use Cases**:
43-
* Applications where minimizing memory usage is the primary goal.
44-
* Read-only datasets with skewed distributions.
45-
* Large-scale data processing where the vector is built once and read many times.
46-
47-
### Summary
48-
49-
| Feature | [`FixedVec`] | [`IntVec`] |
50-
| :------------------ | :--------------------------------------------- | :------------------------------------------- |
51-
| **Encoding** | Fixed-Width | Variable-Width (Instantaneous Codes) |
52-
| **Random Access** | O(1) (direct computation) | O(k) (amortized via sampling) |
53-
| **Mutability** | Yes (`push`, `set`, etc.) | No (immutable after creation) |
54-
| **Atomic Support** | Yes ([`AtomicFixedVec`]) | No (read-only concurrency) |
55-
5636
[`FixedVec`]: https://docs.rs/compressed-intvec/latest/compressed_intvec/fixed/struct.FixedVec.html
5737
[`IntVec`]: https://docs.rs/compressed-intvec/latest/compressed_intvec/variable/struct.IntVec.html
5838
[`AtomicFixedVec`]: https://docs.rs/compressed-intvec/latest/compressed_intvec/fixed/atomic/struct.AtomicFixedVec.html
5939
[`std::vec::Vec`]: https://doc.rust-lang.org/std/vec/struct.Vec.html
6040

6141
## Quick Start
6242

63-
The following examples demonstrate the primary use cases for [`FixedVec`] and [`IntVec`]. All common types and traits are available through the [`prelude`].
43+
The following examples shows some use cases for [`FixedVec`] and [`IntVec`]. All common types and traits are available through the [`prelude`].
6444

65-
### Example: `FixedVec` for Uniform Data and Mutable Access
45+
### Example: [`FixedVec`] for Uniform Data and Mutable Access
6646

67-
Use [`FixedVec`] when data is uniformly distributed or when you require in-place modification or atomic operations.
6847

6948
```rust
7049
use compressed_intvec::prelude::*;
@@ -86,8 +65,6 @@ assert_eq!(vec.get(2), Some(35));
8665

8766
### Example: `IntVec` for High-Ratio Compression
8867

89-
Use [`IntVec`] when minimizing memory usage is the priority and the data is read-only. It is most effective on data with skewed distributions.
90-
9168
```rust
9269
use compressed_intvec::prelude::*;
9370

@@ -113,9 +90,9 @@ assert_eq!(intvec.get(3), Some(1000));
11390
[`LEIntVec`]: https://docs.rs/compressed-intvec/latest/compressed_intvec/variable/type.LEIntVec.html
11491
[`VariableCodecSpec::Auto`]: https://docs.rs/compressed-intvec/latest/compressed_intvec/variable/codec/enum.VariableCodecSpec.html#variant.Auto
11592

116-
## Compression with `IntVec`
93+
## Compression with [`IntVec`]
11794

118-
[`IntVec`] is the optimal choice when data that is not uniformly distributed and we want to minimize memory usage. It uses variable-length codes to represent integers, allowing for significant space savings compared to fixed-width encodings.
95+
[`IntVec`] is the optimal choice when data that is not uniformly distributed and we want to minimize memory usage. It uses variable-length codes to represent integers.
11996

12097
The compression strategy is controlled by the [`VariableCodecSpec`] enum, passed to the builder.
12198

@@ -126,19 +103,19 @@ For most use cases, the recommended strategy is [`VariableCodecSpec::Auto`], whi
126103
| `VariableCodecSpec` Variant | Description & Encoding Strategy | Optimal Data Distribution |
127104
| :--- | :--- | :--- |
128105
| **`Auto`** | **Recommended default.** Analyzes the data to choose the best variable-length code, balancing build time and compression ratio. | Agnostic; adapts to the input data. |
129-
| `Gamma` (γ) | A universal, parameter-free code. Encodes `n` using the unary code of log₂(*n*+1), followed by the remaining bits of `n`+1. | Implied distribution is ≈ 1/(2*x*²). Optimal for data skewed towards small non-negative integers. |
130-
| `Delta` (δ) | A universal, parameter-free code. Encodes `n` using the γ code of log₂(*n*+1), making it more efficient than γ for larger values. | Implied distribution is ≈ 1/(2*x*(log *x*)²). |
106+
| `Gamma` (γ) | A universal, parameter-free code. Encodes `n` using the unary code of log₂(*n*+1), followed by the remaining bits of `n`+1. | Implied distribution is ≈ 1/(2*x*²). Optimal for data skewed towards small non-negative integers. |
107+
| `Delta` (δ) | A universal, parameter-free code. Encodes `n` using the γ code of log₂(*n*+1) , making it more efficient than γ for larger values. | Implied distribution is ≈ 1/(2*x*(log *x*)²). |
131108
| `Rice` | A fast, tunable version of Golomb codes where the parameter *b* must be a power of two. Encodes `n` by splitting it into a quotient (stored in unary) and a remainder (stored in binary). | Geometric distributions. |
132109
| `Golomb` | A tunable code, more general than Rice. Encodes `n` by splitting it into a quotient (stored in unary) and a remainder (stored using a minimal binary code). | Geometric distributions. Implied distribution is ≈ 1/*r*ˣ. |
133-
| `Zeta` (ζ) | A tunable code for power-law data. Encodes `n` based on ⌊⌊log₂(*n*+1)/*k* in unary, followed by a minimal binary code for the remainder. | Power-law distributions (e.g., word frequencies, node degrees). Implied distribution is ≈ 1/*x*<sup>1+1/*k*</sup>. |
110+
| `Zeta` (ζ) | A tunable code for power-law data. Encodes `n` based on log₂(*n*+1)/*k* in unary, followed by a minimal binary code for the remainder. | Power-law distributions (e.g., word frequencies, node degrees). Implied distribution is ≈ 1/*x*<sup>1+1/*k*</sup>. |
134111
| `VByteLe`/`Be`| A byte-aligned code that uses a continuation bit to store integers in a variable number of bytes. Fast to decode. The big-endian variant is lexicographical. | Implied distribution is ≈ 1/*x*<sup>8/7</sup>. Good for general-purpose integer data. |
135112
| `Omega` (ω) | A universal, parameter-free code that recursively encodes the length of the binary representation of `n`. | Implied distribution is approximately 1/*x*. Compact for very large numbers. |
136113
| `Unary` | The simplest code. Encodes `n` as `n` zero-bits followed by a one-bit. | Geometric distributions with a very high probability of small values (e.g., boolean flags). |
137114
| `Explicit` | An escape hatch to use any code from the [`dsi-bitstream::codes::Codes`][dsi-bitstream-codes] enum. | Advanced use cases requiring specific, unlisted codes. |
138115

139116
[dsi-bitstream-codes]: https://docs.rs/dsi-bitstream/latest/dsi_bitstream/codes/enum.Codes.html
140117

141-
### Automatic Selection with `VariableCodecSpec::Auto`
118+
### Automatic Selection with [`VariableCodecSpec::Auto`]
142119

143120
The `Auto` strategy removes the guesswork from codec selection. During the build phase, it analyzes the input data and selects the codec that offers the best compression ratio. This introduces a (non negligible) one-time cost for the analysis at construction time. Use the `Auto` codec when you want to create an [`IntVec`] once and read it many times, as the amortized cost of the analysis is negligible compared to the space savings and the performance of subsequent reads.
144121

@@ -153,7 +130,7 @@ If you need to create multiple [`IntVec`] instances at run-time, consider using
153130
[dsi-bitstream-codes]: https://docs.rs/dsi-bitstream/latest/dsi_bitstream/codes/enum.Codes.html
154131
[`mem-dbg`]: https://docs.rs/mem-dbg/latest/mem_dbg/
155132

156-
## `IntVec` Access Patterns
133+
## [`IntVec`] Access Patterns
157134

158135
The access strategy for a compressed [`IntVec`] has significant performance implications. The library provides several methods, each optimized for a specific access pattern. Using the appropriate method is key to achieving high throughput.
159136

@@ -162,7 +139,8 @@ The access strategy for a compressed [`IntVec`] has significant performance impl
162139
For retrieving a batch of elements from a slice of indices.
163140

164141
* **Mechanism**: This method sorts the provided indices to transform a random access pattern into a single, monotonic scan over the compressed data. This approach minimizes expensive bitstream seek operations and leverages data locality.
165-
* **Use Case**: The preferred method for any batch lookup when all indices are known and can be stored in a slice.
142+
143+
This should be your preferred method for any batch lookup when all indices are known and can be stored in a slice.
166144

167145
```rust
168146
use compressed_intvec::prelude::*;
@@ -171,7 +149,7 @@ use rand::Rng; // For random number generation
171149
let data: Vec<u64> = (0..10_000).collect();
172150
let intvec: LEIntVec = IntVec::builder()
173151
.codec(VariableCodecSpec::Delta)
174-
.k(64)
152+
.k(32)
175153
.build(&data)
176154
.unwrap();
177155

@@ -189,7 +167,8 @@ assert_eq!(values, indices_to_get.iter().map(|&i| data[i]).collect::<Vec<_>>());
189167
For retrieving elements from a streaming iterator of indices.
190168

191169
* **Mechanism**: Processes indices on-the-fly using a stateful [`IntVecSeqReader`] internally, which is optimized for streams with sequential locality.
192-
* **Use Case**: Use when indices cannot be collected into a slice, for example due to memory constraints.
170+
171+
Use when indices cannot be collected into a slice, for example due to memory constraints.
193172

194173
```rust
195174
use compressed_intvec::prelude::*;
@@ -205,14 +184,15 @@ assert_eq!(values, vec![500, 501, 502, 503, 504]);
205184

206185
### Dynamic Lookups: `IntVecReader` and `IntVecSeqReader`
207186

208-
For dynamic or interactive scenarios where lookup indices are not known in advance.
187+
There are interactive scenarios where lookup indices are not known in advance. The library provides two reader types to handle such cases:
209188

210189
#### [`IntVecReader`]: Stateless Random Access
211190

212191
A **stateless** reader for efficient, repeated random lookups.
213192

214193
* **Mechanism**: Amortizes the setup cost of the bitstream reader across multiple calls. Each `get` operation performs an independent seek from the nearest sample point.
215-
* **Use Case**: Optimal for sparse, unpredictable access patterns where there is no locality between consecutive lookups.
194+
195+
Optimal for sparse, unpredictable access patterns where there is no locality between consecutive lookups.
216196

217197
```rust
218198
use compressed_intvec::prelude::*;
@@ -231,7 +211,8 @@ assert_eq!(reader.get(10).unwrap(), Some(10));
231211
A **stateful** reader optimized for access patterns with sequential locality.
232212

233213
* **Mechanism**: Maintains an internal cursor. If a requested index is forward and within the same sample block, it decodes from the last position, avoiding a full seek.
234-
* **Use Case**: Optimal for iterating through sorted or clustered indices where consecutive lookups are near each other.
214+
215+
Optimal for iterating through sorted or clustered indices where consecutive lookups are near each other.
235216

236217
```rust
237218
use compressed_intvec::prelude::*;
@@ -252,15 +233,14 @@ assert_eq!(seq_reader.get(505).unwrap(), Some(505));
252233
[`IntVecReader`]: https://docs.rs/compressed-intvec/latest/compressed_intvec/variable/reader/struct.IntVecReader.html
253234
[`IntVecSeqReader`]: https://docs.rs/compressed-intvec/latest/compressed_intvec/variable/seq_reader/struct.IntVecSeqReader.html
254235

255-
## Concurrency with `AtomicFixedVec`
236+
## Concurrency with [`AtomicFixedVec`]
256237

257-
For concurrent applications, the library provides [`AtomicFixedVec`], a thread-safe variant of [`FixedVec`]. It supports atomic read-modify-write (RMW) operations, enabling safe and efficient manipulation of shared integer data across multiple threads.
238+
For concurrent applications, the library provides [`AtomicFixedVec`], a thread-safe variant of [`FixedVec`]. It supports atomic read-modify-write (RMW) operations, enabling safe and efficient manipulation of shared integer data across multiple threads. The atomicity guarantees depend on the element's bit width and its alignment within the underlying `u64` storage words.
258239

259-
* **Mechanism**: The atomicity guarantees depend on the element's bit width and its alignment within the underlying `u64` storage words.
260-
* **Lock-Free Path**: When an element is fully contained within a single `u64` word (common for power-of-two bit widths), operations are performed using lock-free atomic hardware instructions. Performance is optimal as no locks are involved.
261-
* **Locked Path**: When an element's bits span across the boundary of two `u64` words, operations are protected by a fine-grained mutex from a striped lock pool. This ensures atomicity for the two-word update without resorting to a global lock.
240+
* **Lock-Free Path**: When an element is fully contained within a single `u64` word (guaranteed for power-of-two bit widths), operations are performed using lock-free atomic instructions. Performance here is optimal as no locks are involved.
241+
* **Locked Path**: When an element's bits span across the boundary of two `u64` words (common for non-power-of-two bit widths), operations are protected by a fine-grained mutex from a striped lock pool. This ensures atomicity for the two-word update without resorting to a global lock.
262242

263-
* **Use Case**: Ideal for shared counters, parallel data processing, and any scenario requiring multiple threads to read from and write to the same integer vector concurrently. For write-heavy workloads, configuring the bit width to a power of two (e.g., 8, 16, 32) is recommended to ensure all operations remain on the lock-free path.
243+
Ideal for shared counters, parallel data processing, and any scenario requiring multiple threads to read from and write to the same integer vector concurrently. For write-heavy workloads, configuring the bit width to a power of two (e.g., 8, 16, 32) is recommended to ensure all operations remain on the lock-free path.
264244

265245
### Example: Concurrent Atomic Operations
266246

@@ -323,7 +303,7 @@ fn main() {
323303
println!("Size of the uncompressed Vec<u64>:");
324304
data.mem_dbg(DbgFlags::HUMANIZE | DbgFlags::PERCENTAGE | DbgFlags::RUST_LAYOUT);
325305

326-
// Create an IntVec with a generic Gamma encoding.
306+
// Create an IntVec with Gamma encoding.
327307
let gamma_intvec = LEIntVec::builder()
328308
.codec(VariableCodecSpec::Gamma)
329309
.build(&data)
@@ -355,12 +335,12 @@ fn main() {
355335

356336
The output displays memory breakdown for a 1,000,000-element vector of `u64` integers, uniformly distributed between 0 and 2<sup>20</sup>
357337

358-
- Standard `Vec<u64>`: 80.02 kB (8 bytes per element)
338+
- `Vec<u64>`: 80.02 kB (8 bytes per element)
359339
- [`IntVec`] with Gamma encoding: 47.10 kB (41% reduction)
360340
- [`IntVec`] with Auto codec selection: 28.33 kB (65% reduction, selected Zeta k=10)
361341
- [`FixedVec`] with minimal bit width: 25.06 kB (69% reduction, 20 bits per element)
362342

363-
The memory analysis reveals the internal structure of each data type, including storage overhead for metadata, sampling structures, and encoding parameters.
343+
The memory analysis also shows the internal structure of each data type, including storage overhead for metadata, sampling structures, and encoding parameters.
364344

365345
```text
366346
Size of the uncompressed Vec<u64>:
@@ -405,23 +385,6 @@ Size of the FixedVec with minimal bit width:
405385
0 B 0.00% ╰╴_phantom
406386
```
407387

408-
## Cargo Features
409-
410-
The library's functionality can be customized through the following Cargo features.
411-
412-
### `parallel` (Default Feature)
413-
414-
Enables parallel operations using the [Rayon] crate.Provides `par_iter` and `par_get_many` methods on [`FixedVec`] and [`IntVec`], as well as `par_iter` and `par_iter_mut` on [`AtomicFixedVec`].
415-
416-
This feature is enabled by default, as it significantly improves performance for batch operations on large vectors.
417-
418-
### `serde`
419-
420-
Enables serialization and deserialization for [`FixedVec`], [`AtomicFixedVec`], and [`IntVec`] by implementing the `Serialize` and `Deserialize` traits from the [Serde] framework.
421-
422-
This feature is not enabled by default.
423-
424-
425388
[Rayon]: https://docs.rs/rayon/latest/rayon/
426389
[Serde]: https://serde.rs/
427390
[`FixedVec`]: https://docs.rs/compressed-intvec/latest/compressed_intvec/fixed/struct.FixedVec.html
@@ -436,7 +399,7 @@ The library includes benchmarks for both [`FixedVec`] and [`IntVec`]. It also te
436399
cargo bench
437400
```
438401

439-
The benchmarks measure the performance of random access, batch access, iter_access, and memory usage for various data distributions and vector sizes. For a visual representation of the random_access performance, you can then run the following command:
402+
The benchmarks measure the performance of random access, batch access, iter_access, and memory usage for various data distributions and vector sizes. For a visual representation of the random access performance, run the following command:
440403

441404
```bash
442405
pip3 install -r python/requirements.txt

python/requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pandas==2.3.1
2+
plotly==6.2.0

0 commit comments

Comments
 (0)