Skip to content

Commit 675a6f9

Browse files
committed
add architecture-dependent Storable implementations for usize and isize
1 parent 61f3c02 commit 675a6f9

File tree

9 files changed

+222
-67
lines changed

9 files changed

+222
-67
lines changed

Cargo.lock

Lines changed: 0 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,21 @@ name = "compressed-intvec"
33
version = "0.5.0"
44
edition = "2021"
55
authors = ["Luca Lombardo"]
6-
description = "A compressed integer vector with fast random access that stores values with instantaneous codes in a bitstream"
6+
description = "Space-efficient integer vectors for Rust. Offers a fixed-width implementation for O(1) mutable and atomic access, and a variable-width implementation that uses instantaneous codes and sampling for high compression ratios on non-uniform data."
77
readme = "README.md"
88
license = "Apache-2.0"
99
repository = "https://github.com/lukefleed/compressed-intvec"
10-
keywords = [
11-
"compression",
12-
"vector",
13-
"bitstream",
14-
"data-compression",
15-
"integer-encoding",
16-
]
10+
keywords = ["compression", "vector", "integer", "data-structures", "succinct"]
1711
categories = ["data-structures", "compression", "algorithms"]
1812
exclude = [".github/*", "images/*", "python/*"]
1913

2014
[dependencies]
2115
atomic = "0.5.3"
2216
bytemuck = { version = "1.23.1", optional = true }
2317
common_traits = "0.12.0"
24-
dsi-bitstream = {version = "0.5.0", features = ["mem_dbg"]}
18+
dsi-bitstream = { version = "0.5.0", features = ["mem_dbg"] }
2519
mem_dbg = "0.3.0"
2620
num-traits = "0.2.19"
27-
num_cpus = "1.17.0"
2821
parking_lot = "0.12.4"
2922
rayon = { version = "1.10.0", optional = true }
3023
serde = { version = "1.0.219", features = ["derive"], optional = true }
@@ -117,6 +110,7 @@ path = "benches/fixed/bench_random_write.rs"
117110
default = ["parallel"]
118111
parallel = ["rayon"]
119112
serde = ["dep:serde"]
113+
arch-dependent-storable = []
120114

121115

122116
# --- Build Profiles ---

README.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,24 @@ The resulting SVGs file will be saved in the `images` directory.
411411
[`sux::BitFieldVec`]: https://docs.rs/sux/latest/sux/bits/bit_field_vec/index.htmll
412412
[`succinct::IntVector`]: https://docs.rs/succinct/latest/succinct/int_vec/trait.IntVec.html
413413

414+
## Optional Features
415+
416+
### `arch-dependent-storable`: Storing `usize` and `isize`
417+
418+
By default, [`variable::IntVec`] only supports integer types with a fixed size (e.g., `u32`, `i64`). This guarantees that compressed data is portable across different machine architectures (e.g., from a 64-bit server to a 32-bit embedded device).
419+
420+
The `arch-dependent-storable` feature flag enables [`Storable`] implementations for `usize` and `isize`. When activated, you can create an `IntVec<usize>` directly.
421+
422+
**Warning**: This feature breaks data portability. An `IntVec<usize>` created on a 64-bit system containing values larger than `u32::MAX` will cause a panic if deserialized or read on a 32-bit system. Only enable this feature if you can guarantee that your application and its data will only ever run on a single target architecture (e.g., `x86_64`).
423+
424+
Enable it in your `Cargo.toml`:
425+
```toml
426+
compressed-intvec = { version = "0.5.0", features = ["arch-dependent-storable"] }
427+
```
428+
429+
[`Storable`]: https://docs.rs/compressed-intvec/latest/compressed_intvec/variable/traits/trait.Storable.html
430+
[`variable::IntVec`]: https://docs.rs/compressed-intvec/latest/compressed_intvec/variable/struct.IntVec.html
431+
414432
# TODO
415433

416434
* [ ] Add support for [`epsilon-serde`](https://crates.io/crates/epserde)
417-
* [ ] Add SIMD feature

benches/fixed/benchmark_lock_free_access.rs

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,19 @@ const OPS_PER_THREAD: usize = 100_000;
1313
const BIT_WIDTH: usize = 16; // Power of two for the lock-free path
1414

1515
fn benchmark_lock_free_scaling(c: &mut Criterion) {
16-
let mut thread_counts: Vec<usize> = (1..=num_cpus::get())
17-
.filter(|n| n.is_power_of_two())
18-
.collect();
19-
if !thread_counts.contains(&num_cpus::get()) {
20-
thread_counts.push(num_cpus::get());
16+
// Determine the number of logical cores available.
17+
let num_cores = std::thread::available_parallelism().unwrap().get();
18+
let mut thread_counts: Vec<usize> = (1..=num_cores).filter(|n| n.is_power_of_two()).collect();
19+
if !thread_counts.contains(&num_cores) {
20+
thread_counts.push(num_cores);
2121
}
2222
thread_counts.sort_unstable();
2323
thread_counts.dedup();
2424

2525
for &num_threads in &thread_counts {
2626
let total_ops = (OPS_PER_THREAD * num_threads) as u64;
27-
let mut group = c.benchmark_group(format!("LockFreeScaling_Diffuse/{}Threads", num_threads));
27+
let mut group =
28+
c.benchmark_group(format!("LockFreeScaling_Diffuse/{}Threads", num_threads));
2829
group.throughput(Throughput::Elements(total_ops));
2930

3031
// Pre-generate a single set of random indices for this benchmark configuration.
@@ -34,14 +35,22 @@ fn benchmark_lock_free_scaling(c: &mut Criterion) {
3435
.collect();
3536

3637
// --- Setup Data Structures Once ---
37-
let baseline_u16 = Arc::new((0..VECTOR_SIZE).map(|_| AtomicU16::new(0)).collect::<Vec<_>>());
38+
let baseline_u16 = Arc::new(
39+
(0..VECTOR_SIZE)
40+
.map(|_| AtomicU16::new(0))
41+
.collect::<Vec<_>>(),
42+
);
3843
let afv_16bit = Arc::new(
3944
UAtomicFixedVec::<u64>::builder()
4045
.bit_width(BitWidth::Explicit(BIT_WIDTH))
4146
.build(&vec![0; VECTOR_SIZE])
4247
.unwrap(),
4348
);
44-
let sux_storage_16bit = Arc::new((0..(VECTOR_SIZE * BIT_WIDTH).div_ceil(64) + 2).map(|_| AtomicU64::new(0)).collect());
49+
let sux_storage_16bit = Arc::new(
50+
(0..(VECTOR_SIZE * BIT_WIDTH).div_ceil(64) + 2)
51+
.map(|_| AtomicU64::new(0))
52+
.collect(),
53+
);
4554

4655
// --- Benchmark Runs ---
4756
group.bench_function("Baseline_Vec<AtomicU16>/store", |b| {
@@ -78,7 +87,11 @@ fn run_store_on_atomic_u16(vec: &Arc<Vec<AtomicU16>>, num_threads: usize, indice
7887
});
7988
}
8089

81-
fn run_store_on_atomic_fixed_vec(vec: &Arc<UAtomicFixedVec<u64>>, num_threads: usize, indices: &[usize]) {
90+
fn run_store_on_atomic_fixed_vec(
91+
vec: &Arc<UAtomicFixedVec<u64>>,
92+
num_threads: usize,
93+
indices: &[usize],
94+
) {
8295
let barrier = Arc::new(Barrier::new(num_threads));
8396
let chunks: Vec<_> = indices.chunks(OPS_PER_THREAD).collect();
8497

@@ -105,7 +118,13 @@ fn run_store_on_sux_vec(storage: &Arc<Vec<AtomicU64>>, num_threads: usize, indic
105118
let storage_clone = Arc::clone(storage);
106119
let barrier_clone = Arc::clone(&barrier);
107120
s.spawn(move || {
108-
let sux_vec = unsafe { AtomicBitFieldVec::<u64, _>::from_raw_parts(storage_clone.as_slice(), BIT_WIDTH, VECTOR_SIZE) };
121+
let sux_vec = unsafe {
122+
AtomicBitFieldVec::<u64, _>::from_raw_parts(
123+
storage_clone.as_slice(),
124+
BIT_WIDTH,
125+
VECTOR_SIZE,
126+
)
127+
};
109128
barrier_clone.wait();
110129
for &index in *chunk {
111130
unsafe {
@@ -125,4 +144,4 @@ criterion_group! {
125144
.measurement_time(Duration::from_secs(3));
126145
targets = benchmark_lock_free_scaling
127146
}
128-
criterion_main!(benches);
147+
criterion_main!(benches);

benches/fixed/benchmark_locked_access.rs

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ const OPS_PER_THREAD: usize = 100_000;
1313
const BIT_WIDTH: usize = 15; // Non-power of two to force the locked path
1414

1515
fn benchmark_locked_scaling(c: &mut Criterion) {
16-
let mut thread_counts: Vec<usize> = (1..=num_cpus::get())
17-
.filter(|n| n.is_power_of_two())
18-
.collect();
19-
if !thread_counts.contains(&num_cpus::get()) {
20-
thread_counts.push(num_cpus::get());
16+
// Determine the number of logical cores available.
17+
let num_cores = std::thread::available_parallelism().unwrap().get();
18+
let mut thread_counts: Vec<usize> = (1..=num_cores).filter(|n| n.is_power_of_two()).collect();
19+
if !thread_counts.contains(&num_cores) {
20+
thread_counts.push(num_cores);
2121
}
2222
thread_counts.sort_unstable();
2323
thread_counts.dedup();
@@ -34,14 +34,22 @@ fn benchmark_locked_scaling(c: &mut Criterion) {
3434
.collect();
3535

3636
// --- Setup Data Structures Once ---
37-
let baseline_u16 = Arc::new((0..VECTOR_SIZE).map(|_| AtomicU16::new(0)).collect::<Vec<_>>());
37+
let baseline_u16 = Arc::new(
38+
(0..VECTOR_SIZE)
39+
.map(|_| AtomicU16::new(0))
40+
.collect::<Vec<_>>(),
41+
);
3842
let afv_15bit = Arc::new(
3943
UAtomicFixedVec::<u64>::builder()
4044
.bit_width(BitWidth::Explicit(BIT_WIDTH))
4145
.build(&vec![0; VECTOR_SIZE])
4246
.unwrap(),
4347
);
44-
let sux_storage_15bit = Arc::new((0..(VECTOR_SIZE * BIT_WIDTH).div_ceil(64) + 2).map(|_| AtomicU64::new(0)).collect());
48+
let sux_storage_15bit = Arc::new(
49+
(0..(VECTOR_SIZE * BIT_WIDTH).div_ceil(64) + 2)
50+
.map(|_| AtomicU64::new(0))
51+
.collect(),
52+
);
4553

4654
// --- Benchmark Runs ---
4755
group.bench_function("Baseline_Vec<AtomicU16>/store", |b| {
@@ -78,7 +86,11 @@ fn run_store_on_atomic_u16(vec: &Arc<Vec<AtomicU16>>, num_threads: usize, indice
7886
});
7987
}
8088

81-
fn run_store_on_atomic_fixed_vec(vec: &Arc<UAtomicFixedVec<u64>>, num_threads: usize, indices: &[usize]) {
89+
fn run_store_on_atomic_fixed_vec(
90+
vec: &Arc<UAtomicFixedVec<u64>>,
91+
num_threads: usize,
92+
indices: &[usize],
93+
) {
8294
let barrier = Arc::new(Barrier::new(num_threads));
8395
let chunks: Vec<_> = indices.chunks(OPS_PER_THREAD).collect();
8496

@@ -105,7 +117,13 @@ fn run_store_on_sux_vec(storage: &Arc<Vec<AtomicU64>>, num_threads: usize, indic
105117
let storage_clone = Arc::clone(storage);
106118
let barrier_clone = Arc::clone(&barrier);
107119
s.spawn(move || {
108-
let sux_vec = unsafe { AtomicBitFieldVec::<u64, _>::from_raw_parts(storage_clone.as_slice(), BIT_WIDTH, VECTOR_SIZE) };
120+
let sux_vec = unsafe {
121+
AtomicBitFieldVec::<u64, _>::from_raw_parts(
122+
storage_clone.as_slice(),
123+
BIT_WIDTH,
124+
VECTOR_SIZE,
125+
)
126+
};
109127
barrier_clone.wait();
110128
for &index in *chunk {
111129
unsafe {
@@ -125,4 +143,4 @@ criterion_group! {
125143
.measurement_time(Duration::from_secs(3));
126144
targets = benchmark_locked_scaling
127145
}
128-
criterion_main!(benches);
146+
criterion_main!(benches);

0 commit comments

Comments
 (0)