diff --git a/CHANGELOG.md b/CHANGELOG.md index a438b90..6bddc39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,54 @@ # Changelog ## Unreleased -- Added `WaveletMatrix::to_bytes` and `WaveletMatrix::from_bytes` returning metadata and bytes for zero-copy persistence. -- Documented the serialized `WaveletMatrix` layout with ASCII art. -- Added `CompactVector::to_bytes` and `from_bytes` for zero-copy serialization. +- Introduced a `Serializable` trait for metadata-based reconstruction and + implemented it for `CompactVector`, `DacsByte`, and `WaveletMatrix`. +- Audited `DacsByte` and `WaveletMatrix` to leverage `SectionHandle::view` + during deserialization, removing legacy `slice_to_bytes` helpers and fully + adopting the `ByteArea`-backed reconstruction path. +- Switched internal bit-vector words and handles from `usize` to `u64`, removing + unsafe handle transmutes in `WaveletMatrixBuilder` and fixing word size to + 64-bit. + - Reversed remaining layers and popped in `WaveletMatrixBuilder::freeze` + to avoid repeated vector shifts. + - `WaveletMatrixMeta` now stores a handle slice of per-layer handles, and + `WaveletMatrixBuilder` allocates that slice from the `SectionWriter`. + - `WaveletMatrixBuilder::with_capacity` records each layer's handle up front, + eliminating handle assignment during `freeze`. + - Switched to the zerocopy `SectionHandle` from `anybytes`, removing the + interim `HandleRepr` shim. + - Added `WaveletMatrixBuilder` for fixed-size construction, writing raw bits per + layer and stably partitioning them on `freeze`; `WaveletMatrix::from_iter` + now builds via this builder without requiring iterator cloning. + - `WaveletMatrix` construction now goes through `from_iter`, which allocates + layer bitvectors from a `SectionWriter` and consumes a single + `ExactSizeIterator` without temporary `CompactVector` partitions. + - `CompactVector::iter` now implements `ExactSizeIterator` to support the new + constructor. + - `WaveletMatrixBuilder::freeze` partitions layers in place, removing the + temporary bit buffer previously used during construction. + - Removed `order` and `next_order` buffers by sorting remaining layers in + place during each `freeze` step. + - Optimized `WaveletMatrixBuilder::freeze` using stable per-layer partitions + and cycle-based permutations, reducing layer processing to linear time. + - Replaced the `perm` array with a scratch `visited` bitmap and cycle + rotations so each level permutes lower layers in place with only `O(n)` + extra bits. + - Stored row suffix bits in a `usize` during cycle rotations, removing the + temporary `Vec` from `rotate_cycle_over_lower_levels`. + - Reused `BitVectorBuilder` as the scratch `visited` bitmap for + wavelet-matrix construction, eliminating the separate `BitArrayBuilder`. + - Added `swap_bits` helper to `BitVectorBuilder` for in-place bit exchanges. + - Reworked `WaveletMatrix::from_iter` to require a cloneable iterator and + build layers in two passes without temporary buffers. +- Rewrote `CompactVectorBuilder` to use fixed-size `set_int` and `set_ints` + APIs, removing `push_int`/`extend` and updating builders and examples. +- Added `with_capacity` constructor on `BitVectorBuilder` and honored capacity in + `CompactVectorBuilder::with_capacity` to pre-allocate bit storage. +- Replaced `BitVectorBuilder::new` with `with_capacity` that allocates from an + `anybytes::ByteArea` section and plumbed `SectionWriter` through + `CompactVectorBuilder` and wavelet matrix builders. +- Builders now track capacity and error when pushes exceed the reserved size. - Made `DacsByte` generic over its flag index type with a default of `Rank9SelIndex`. - `DacsByte::from_slice` now accepts a generic index type, removing `from_slice_with_index`. - Added `BitVectorBuilder` and zero-copy `BitVectorData` backed by `anybytes::View`. @@ -12,11 +57,15 @@ - Rename crate from `succdisk` to `jerky`. - Replaced the old `BitVector` with the generic `BitVector` and renamed the mutable variant to `RawBitVector`. -- Extended `BitVectorBuilder` with `push_bits` and `set_bit` APIs. +- Replaced the push-based `BitVectorBuilder` with fixed-size `set_bit` and `set_bits` APIs and updated builders accordingly. +- Added `set_bits_from_iter` to `BitVectorBuilder` and later revised it to take a + start offset and consume bits until the iterator ends or the builder is + full, leaving any unconsumed items to the caller. - Added `from_bit` constructor on `BitVectorBuilder` for repeating a single bit. - `DacsByte` now stores level data as zero-copy `View<[u8]>` values. -- Added `to_bytes` and `from_bytes` on `DacsByte` for zero-copy serialization. -- Documented the byte layout produced by `DacsByte::to_bytes` with ASCII art. +- Replaced `to_bytes` helpers with `metadata` methods returning `SectionHandle`s + so structures can be reconstructed zero-copy via `from_bytes`. +- Documented the byte layout for `DacsByte` sequences with ASCII art. - Switched `anybytes` dependency to track the upstream Git repository for the latest changes. - Removed internal byte buffers from data structures; `WaveletMatrix`, @@ -31,13 +80,15 @@ - `Rank9Sel` now stores a `BitVector` built via `BitVectorBuilder`. - Replaced `DArrayFullIndex` with new `DArrayIndex` that uses const generics to optionally include `select1` and `select0` support. -- Introduced `CompactVectorBuilder` mutable APIs `push_int`, `set_int`, and `extend`. +- Introduced `CompactVectorBuilder` mutable APIs `set_int` and `set_ints`. - Simplified bit vector imports by re-exporting `BitVectorBuilder` and `Rank9SelIndex` and updating examples. - Moved the `bit_vector::bit_vector` module contents directly into `bit_vector` for cleaner paths. +- Recorded future work items for a metadata serialization trait and + ByteArea-backed documentation examples. - Added README usage example demonstrating basic bit vector operations. - Removed `bit_vector::prelude`; import traits directly with `use jerky::bit_vector::*`. - Added `freeze()` on `CompactVectorBuilder` yielding an immutable `CompactVector` backed by `BitVector`. -- `CompactVector::new` and `with_capacity` now return builders; other constructors build via the builder pattern. +- Removed `CompactVector::new`; use `with_capacity` to construct builders. - Wavelet matrix and DACs builders now use `BitVectorBuilder` for temporary bit vectors, storing only immutable `BitVector` data after construction. - Removed obsolete `RawBitVector` type. @@ -66,3 +117,13 @@ - Documented `WaveletMatrix` usage in `README.md`. - Moved README usage examples to runnable files in `examples/`. - Added `compact_vector` example showing construction and retrieval. +- Serialized `WaveletMatrix` and `DacsByte` directly into a `ByteArea` to + avoid intermediate copies and guarantee contiguous layout. +- Enabled doctests for `WaveletMatrix` by removing `ignore` fences from its + documentation examples. +- `DacsByte::from_slice` now writes level bytes and flags directly into + `SectionWriter` buffers, removing the intermediate `Vec` allocations. +- Stored per-level `DacsByte` handles in the byte arena, allowing + `DacsByteMeta` to reference a single handle slice like `WaveletMatrixMeta`. +- Expanded examples and README with `ByteArea`/`SectionHandle` metadata + reconstruction for set-based APIs, adding a `dacs_byte` usage demo. diff --git a/Cargo.toml b/Cargo.toml index 497bc68..9709df1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ rust-version = "1.61.0" [dependencies] anyhow = "1.0" num-traits = "0.2.15" -anybytes = { git = "https://github.com/triblespace/anybytes", features = ["zerocopy"] } +anybytes = { git = "https://github.com/triblespace/anybytes", features = ["zerocopy", "mmap"] } zerocopy = "0.8" [features] diff --git a/INVENTORY.md b/INVENTORY.md index f726f62..ce43e75 100644 --- a/INVENTORY.md +++ b/INVENTORY.md @@ -9,9 +9,44 @@ - Investigate alternative dense-select index strategies to replace removed `DArrayIndex`. - Explore additional index implementations leveraging the new generic `DacsByte`. - Demonstrate the generic `from_slice` usage in examples and docs. -- Showcase `DacsByte` byte serialization in an example. -- Provide serialization helpers for additional structures beyond `WaveletMatrix`. -- Show `CompactVector::to_bytes` and `from_bytes` in examples. +- Apply `with_capacity` constructors across builders to avoid intermediate reallocations. +- Transition builders to fixed-size APIs, removing growable variants. +- Refactor builders and serializers to operate on `ByteArea` sections, enabling + zero-copy persistence across all structures. +- Move `DacsByte` metadata arrays into the arena and store per-level handles + similar to `WaveletMatrixMeta`. +- Add slice-based range setters for integer builders to minimize manual index + tracking during construction. +- Provide bulk bit setters like `set_bits_from_slice` for `BitVectorBuilder` + to copy from packed data efficiently. +- Provide convenience helpers to manage `ByteArea` and `SectionWriter` setup for + common builder use cases. +- Audit remaining constructors for zero-capacity variants and decide whether to + offer explicit `empty` helpers instead of `with_capacity(0)`. +- Allocate temporary wavelet-matrix buffers from `ByteArea` to avoid + intermediate `Vec` copies and ensure fully contiguous construction. +- Provide a derive or macro to reduce boilerplate when implementing the + `Serializable` trait. +- Consider a slice-based `WaveletMatrix` constructor to avoid requiring + cloneable iterators. + - Benchmark the cycle-based partitioning in `WaveletMatrixBuilder::freeze` + and explore more efficient permutation strategies. +- Explore specialized rotation helpers for `BitVectorBuilder` to speed up + recursive partitioning without extra buffers. +- Explore using `BitVectorBuilder` for other temporary bitmaps to reduce + scattered `Vec` allocations. +- Review documentation examples across modules and convert remaining ignored + snippets into runnable doctests. +- Explore iterating layer indices instead of reversing `remaining` to avoid + the upfront `reverse` cost in `WaveletMatrixBuilder::freeze`. +- Audit integer-vector constructors for opportunities to allocate directly + from `SectionWriter` without temporary `Vec`s. +- Document the fixed 64-bit word assumption across structures now that bit + vectors use `u64` internally. +- Provide helpers on `SectionHandle` to derive typed sub-handles, reducing + manual offset math in complex `from_bytes` implementations like `DacsByte`. +- Investigate slimming `DacsByte` per-level metadata to avoid storing unused + flag handles for the last level. ## Discovered Issues - `katex.html` performs manual string replacements; consider DOM-based manipulation. diff --git a/README.md b/README.md index e01008d..dad9e0a 100644 --- a/README.md +++ b/README.md @@ -25,17 +25,22 @@ RUSTDOCFLAGS="--html-in-header katex.html" cargo doc --no-deps ## Zero-copy bit vectors `BitVectorBuilder` can build a bit vector whose underlying `BitVectorData` -is backed by `anybytes::View`. The data can be serialized with -`BitVectorData::to_bytes` and reconstructed using `BitVectorData::from_bytes`, -allowing zero-copy loading from an mmap or any other source by passing the -byte region to `Bytes::from_source`. +is backed by `anybytes::View`. Metadata describing a stored sequence includes +[`SectionHandle`](anybytes::area::SectionHandle)s so the raw +`Bytes` returned by `ByteArea::freeze` can be handed to +`BitVectorData::from_bytes` for zero‑copy reconstruction. -`DacsByte` sequences support a similar interface with `to_bytes` returning -metadata alongside the byte slice and `from_bytes` rebuilding the sequence -using that metadata. +Types following this pattern implement the [`Serializable`](src/serialization.rs) trait, +which exposes a `metadata` accessor and a `from_bytes` constructor. + +`DacsByte` sequences expose a `metadata` method returning a descriptor with a +handle to a slice of per-level handles. Each entry stores the flag bitvector +handle (if any), its bit length, and the payload byte handle. `from_bytes` +rebuilds the sequence using that metadata. ```text -Bytes layout from `DacsByte::to_bytes`: +Bytes layout for a `DacsByte` sequence (current builders place sections +contiguously, though layout is fully described by the stored handles): | flag[0] words | flag[1] words | ... | flag[n-2] words | level[0] data | level[1] data | ... | level[n-1] data | @@ -43,30 +48,34 @@ The flag vectors come first and store native-endian `usize` words. The level data immediately follows without any padding. ``` -`CompactVector` offers similar helpers: `CompactVector::to_bytes` returns a -metadata struct along with the raw bytes, and `CompactVector::from_bytes` -reconstructs the vector from that information. - -`WaveletMatrix` sequences share this layout and can be serialized with -`WaveletMatrix::to_bytes` (returning metadata and bytes) and reconstructed -using `WaveletMatrix::from_bytes`. - -The byte buffer returned by `to_bytes` stores each bit-vector layer -contiguously. Given `num_words = ceil(len / WORD_LEN)`, the layout is: - -``` -bytes: -+------------+------------+-----+ -| layer 0 | layer 1 | ... | -| num_words | num_words | | -+------------+------------+-----+ +`CompactVector` and `WaveletMatrix` provide the same pattern: call `metadata` +to obtain a descriptor with the required `SectionHandle`s, then hand both the +metadata and the full `Bytes` region to `from_bytes`. + +For a wavelet matrix the metadata stores a handle to a slice of per-layer +handles. Each handle in that slice points to the native-endian `usize` words +forming a single layer. Layers may reside anywhere in the arena and no longer +need to be contiguous. + +```rust +use anybytes::ByteArea; +use jerky::int_vectors::{CompactVector, CompactVectorBuilder}; + +let mut area = ByteArea::new()?; +let mut sections = area.sections(); +let mut builder = CompactVectorBuilder::with_capacity(3, 3, &mut sections)?; +builder.set_ints(0..3, [7, 2, 5])?; +let cv = builder.freeze(); +let meta = cv.metadata(); +let bytes = area.freeze()?; +let view = CompactVector::from_bytes(meta, bytes.clone())?; +assert_eq!(view.get_int(1), Some(2)); ``` -where each segment contains `num_words` consecutive `usize` words for a layer. ## Examples See the [examples](examples/) directory for runnable usage demos, including -`bit_vector`, `wavelet_matrix`, and `compact_vector`. +`bit_vector`, `compact_vector`, `dacs_byte`, and `wavelet_matrix`. ## Licensing diff --git a/bench/benches/timing_bitvec_rank.rs b/bench/benches/timing_bitvec_rank.rs index d29bf03..70095e9 100644 --- a/bench/benches/timing_bitvec_rank.rs +++ b/bench/benches/timing_bitvec_rank.rs @@ -7,6 +7,7 @@ use criterion::{ criterion_group, criterion_main, measurement::WallTime, BenchmarkGroup, Criterion, SamplingMode, }; +use anybytes::ByteArea; use jerky::bit_vector::{BitVector, BitVectorBuilder, NoIndex, Rank, Rank9SelIndex}; const SAMPLE_SIZE: usize = 30; @@ -39,15 +40,23 @@ fn run_queries(idx: &R, queries: &[usize]) { fn perform_bitvec_rank(group: &mut BenchmarkGroup, bits: &[bool], queries: &[usize]) { group.bench_function("jerky/BitVector", |b| { - let mut builder = BitVectorBuilder::new(); - builder.extend_bits(bits.iter().cloned()); + let mut area = ByteArea::new().unwrap(); + let mut sections = area.sections(); + let mut builder = BitVectorBuilder::with_capacity(bits.len(), &mut sections).unwrap(); + for (i, &bval) in bits.iter().enumerate() { + builder.set_bit(i, bval).unwrap(); + } let idx: BitVector = builder.freeze(); b.iter(|| run_queries(&idx, &queries)); }); group.bench_function("jerky/BitVector", |b| { - let mut builder = BitVectorBuilder::new(); - builder.extend_bits(bits.iter().cloned()); + let mut area = ByteArea::new().unwrap(); + let mut sections = area.sections(); + let mut builder = BitVectorBuilder::with_capacity(bits.len(), &mut sections).unwrap(); + for (i, &bval) in bits.iter().enumerate() { + builder.set_bit(i, bval).unwrap(); + } let idx = builder.freeze::(); b.iter(|| run_queries(&idx, &queries)); }); diff --git a/bench/benches/timing_bitvec_select.rs b/bench/benches/timing_bitvec_select.rs index bb38e1c..713e614 100644 --- a/bench/benches/timing_bitvec_select.rs +++ b/bench/benches/timing_bitvec_select.rs @@ -3,6 +3,7 @@ use std::time::Duration; use rand::{Rng, SeedableRng}; use rand_chacha::ChaChaRng; +use anybytes::ByteArea; use criterion::{ criterion_group, criterion_main, measurement::WallTime, BenchmarkGroup, Criterion, SamplingMode, }; @@ -42,15 +43,23 @@ fn run_queries(idx: &S, queries: &[usize]) { fn perform_bitvec_select(group: &mut BenchmarkGroup, bits: &[bool], queries: &[usize]) { group.bench_function("jerky/BitVector", |b| { - let mut builder = BitVectorBuilder::new(); - builder.extend_bits(bits.iter().cloned()); + let mut area = ByteArea::new().unwrap(); + let mut sections = area.sections(); + let mut builder = BitVectorBuilder::with_capacity(bits.len(), &mut sections).unwrap(); + for (i, &bval) in bits.iter().enumerate() { + builder.set_bit(i, bval).unwrap(); + } let idx: BitVector = builder.freeze(); b.iter(|| run_queries(&idx, &queries)); }); group.bench_function("jerky/BitVector", |b| { - let mut builder = BitVectorBuilder::new(); - builder.extend_bits(bits.iter().cloned()); + let mut area = ByteArea::new().unwrap(); + let mut sections = area.sections(); + let mut builder = BitVectorBuilder::with_capacity(bits.len(), &mut sections).unwrap(); + for (i, &bval) in bits.iter().enumerate() { + builder.set_bit(i, bval).unwrap(); + } let idx = builder.freeze::(); b.iter(|| run_queries(&idx, &queries)); }); diff --git a/bench/benches/timing_chrseq_access.rs b/bench/benches/timing_chrseq_access.rs index 38a10fd..c8608b5 100644 --- a/bench/benches/timing_chrseq_access.rs +++ b/bench/benches/timing_chrseq_access.rs @@ -3,6 +3,7 @@ use std::time::Duration; use rand::{Rng, SeedableRng}; use rand_chacha::ChaChaRng; +use anybytes::ByteArea; use jerky::bit_vector::*; use jerky::char_sequences::WaveletMatrix; use jerky::int_vectors::CompactVector; @@ -25,8 +26,9 @@ const PROTEINS_PSEF_STR: &str = include_str!("../data/texts/proteins.1MiB.txt"); // In effective alphabet fn load_text(s: &str) -> CompactVector { let mut text = s.as_bytes().to_vec(); - let mut builder = BitVectorBuilder::new(); - builder.extend_bits(core::iter::repeat(false).take(256)); + let mut area = ByteArea::new().unwrap(); + let mut sections = area.sections(); + let mut builder = BitVectorBuilder::with_capacity(256, &mut sections).unwrap(); for &c in &text { builder.set_bit(c as usize, true).unwrap(); } @@ -92,7 +94,11 @@ fn perform_chrseq_access(group: &mut BenchmarkGroup, text: &CompactVec let queries = gen_random_ints(NUM_QUERIES, 0, text.len(), SEED_QUERIES); group.bench_function("jerky/WaveletMatrix", |b| { - let idx = WaveletMatrix::::new(text.clone()).unwrap(); + let alph_size = text.iter().max().unwrap() + 1; + let mut area = ByteArea::new().unwrap(); + let mut sections = area.sections(); + let idx = WaveletMatrix::::from_iter(alph_size, text.iter(), &mut sections) + .unwrap(); b.iter(|| run_queries(&idx, &queries)); }); } diff --git a/bench/benches/timing_intvec_access.rs b/bench/benches/timing_intvec_access.rs index db04cfc..73d0e14 100644 --- a/bench/benches/timing_intvec_access.rs +++ b/bench/benches/timing_intvec_access.rs @@ -7,6 +7,7 @@ use criterion::{ criterion_group, criterion_main, measurement::WallTime, BenchmarkGroup, Criterion, SamplingMode, }; +use anybytes::ByteArea; use jerky::int_vectors::Access; const SAMPLE_SIZE: usize = 30; @@ -87,7 +88,10 @@ fn perform_intvec_access(group: &mut BenchmarkGroup, vals: &[u32]) { }); group.bench_function("jerky/DacsByte", |b| { - let idx = jerky::int_vectors::DacsByte::from_slice(vals).unwrap(); + let mut area = ByteArea::new().unwrap(); + let mut writer = area.sections(); + let idx = jerky::int_vectors::DacsByte::from_slice(vals, &mut writer).unwrap(); + area.freeze().unwrap(); b.iter(|| run_queries(&idx, &queries)); }); } diff --git a/bench/src/mem_bitvec.rs b/bench/src/mem_bitvec.rs index 16cdf67..9efc1f4 100644 --- a/bench/src/mem_bitvec.rs +++ b/bench/src/mem_bitvec.rs @@ -1,3 +1,4 @@ +use anybytes::ByteArea; use jerky::bit_vector::{BitVector, BitVectorBuilder, Rank9SelIndex}; use rand::{Rng, SeedableRng}; use rand_chacha::ChaChaRng; @@ -21,22 +22,30 @@ fn show_memories(p: f64) { println!("[p = {p}]"); let bytes = { - let mut b = BitVectorBuilder::new(); - b.extend_bits(bits.iter().cloned()); + let mut area = ByteArea::new().unwrap(); + let mut sections = area.sections(); + let mut b = BitVectorBuilder::with_capacity(bits.len(), &mut sections).unwrap(); + for (i, &bv) in bits.iter().enumerate() { + b.set_bit(i, bv).unwrap(); + } let idx: BitVector = b.freeze::(); - let (len, data) = idx.data.to_bytes(); - let index = idx.index.to_bytes(); - data.as_ref().len() + index.as_ref().len() + std::mem::size_of_val(&len) + let len = idx.data.len; + let data_bytes = idx.data.words.bytes(); + data_bytes.as_ref().len() + std::mem::size_of_val(&len) }; print_memory("BitVector", bytes); let bytes = { - let mut b = BitVectorBuilder::new(); - b.extend_bits(bits.iter().cloned()); + let mut area = ByteArea::new().unwrap(); + let mut sections = area.sections(); + let mut b = BitVectorBuilder::with_capacity(bits.len(), &mut sections).unwrap(); + for (i, &bv) in bits.iter().enumerate() { + b.set_bit(i, bv).unwrap(); + } let idx: BitVector = b.freeze::(); - let (len, data) = idx.data.to_bytes(); - let index = idx.index.to_bytes(); - data.as_ref().len() + index.as_ref().len() + std::mem::size_of_val(&len) + let len = idx.data.len; + let data_bytes = idx.data.words.bytes(); + data_bytes.as_ref().len() + std::mem::size_of_val(&len) }; print_memory("BitVector (with select hints)", bytes); } diff --git a/bench/src/mem_chrseq.rs b/bench/src/mem_chrseq.rs index 0c52a68..7d83c01 100644 --- a/bench/src/mem_chrseq.rs +++ b/bench/src/mem_chrseq.rs @@ -1,3 +1,4 @@ +use anybytes::ByteArea; use jerky::bit_vector::{BitVector, BitVectorBuilder, NoIndex, Rank, Rank9SelIndex}; use jerky::char_sequences::WaveletMatrix; use jerky::int_vectors::CompactVector; @@ -15,8 +16,9 @@ fn main() { // In effective alphabet fn load_text(s: &str) -> CompactVector { let mut text = s.as_bytes().to_vec(); - let mut builder = BitVectorBuilder::new(); - builder.extend_bits(core::iter::repeat(false).take(256)); + let mut area = ByteArea::new().unwrap(); + let mut sections = area.sections(); + let mut builder = BitVectorBuilder::with_capacity(256, &mut sections).unwrap(); for &c in &text { builder.set_bit(c as usize, true).unwrap(); } @@ -32,14 +34,17 @@ fn show_memories(title: &str, text: &CompactVector) { show_data_stats(text); let bytes = { - let idx = WaveletMatrix::::new(text.clone()).unwrap(); + let alph_size = text.iter().max().unwrap() + 1; + let mut area = ByteArea::new().unwrap(); + let mut sections = area.sections(); + let idx = WaveletMatrix::::from_iter(alph_size, text.iter(), &mut sections) + .unwrap(); let layer_bytes: usize = idx .layers .iter() .map(|bv| { - let (_, data) = bv.data.to_bytes(); - let index = bv.index.to_bytes(); - data.as_ref().len() + index.as_ref().len() + let data_bytes = bv.data.words.bytes(); + data_bytes.as_ref().len() }) .sum(); layer_bytes + std::mem::size_of::() diff --git a/bench/src/mem_intvec.rs b/bench/src/mem_intvec.rs index ac71c4b..d042d0b 100644 --- a/bench/src/mem_intvec.rs +++ b/bench/src/mem_intvec.rs @@ -2,6 +2,8 @@ const DBLP_PSEF_STR: &str = include_str!("../data/lcps/dblp.1MiB.txt"); const DNA_PSEF_STR: &str = include_str!("../data/lcps/dna.1MiB.txt"); const PROTEINS_PSEF_STR: &str = include_str!("../data/lcps/proteins.1MiB.txt"); +use anybytes::ByteArea; + fn parse_ints_from_str(s: &str) -> Vec { let mut ints = vec![]; for l in s.split('\n') { @@ -36,24 +38,26 @@ fn show_memories(title: &str, vals: &[u32]) { let bytes = { let idx = jerky::int_vectors::CompactVector::from_slice(vals).unwrap(); - let (meta, bytes) = idx.to_bytes(); - std::mem::size_of_val(&meta) + bytes.as_ref().len() + let data_bytes = idx.chunks.data.words.bytes(); + data_bytes.as_ref().len() + std::mem::size_of_val(&idx) }; print_memory("CompactVector", bytes, vals.len()); let bytes = { - let idx = jerky::int_vectors::DacsByte::from_slice(vals).unwrap(); + let mut area = ByteArea::new().unwrap(); + let mut writer = area.sections(); + let idx = jerky::int_vectors::DacsByte::from_slice(vals, &mut writer).unwrap(); + area.freeze().unwrap(); let data_bytes: usize = idx.data.iter().map(|lvl| lvl.as_ref().len()).sum(); let flags_bytes: usize = idx .flags .iter() .map(|bv| { - let (_, data) = bv.data.to_bytes(); - let index = bv.index.to_bytes(); - data.as_ref().len() + index.as_ref().len() + let data = bv.data.words.bytes(); + data.as_ref().len() + std::mem::size_of_val(&bv.index) }) .sum(); - data_bytes + flags_bytes + data_bytes + flags_bytes + std::mem::size_of_val(&idx) }; print_memory("DacsByte", bytes, vals.len()); } diff --git a/examples/bit_vector.rs b/examples/bit_vector.rs index c921782..3683823 100644 --- a/examples/bit_vector.rs +++ b/examples/bit_vector.rs @@ -1,8 +1,13 @@ +use anybytes::ByteArea; use jerky::bit_vector::*; fn main() -> Result<(), Box> { - let mut builder = BitVectorBuilder::new(); - builder.extend_bits([true, false, true, false, true]); + let mut area = ByteArea::new()?; + let mut sections = area.sections(); + let mut builder = BitVectorBuilder::with_capacity(5, &mut sections)?; + builder.set_bit(0, true)?; + builder.set_bit(2, true)?; + builder.set_bit(4, true)?; let bv = builder.freeze::(); assert_eq!(bv.num_bits(), 5); diff --git a/examples/compact_vector.rs b/examples/compact_vector.rs index 6134029..beff2f5 100644 --- a/examples/compact_vector.rs +++ b/examples/compact_vector.rs @@ -1,13 +1,19 @@ -use jerky::int_vectors::CompactVectorBuilder; +use anybytes::ByteArea; +use jerky::int_vectors::{CompactVector, CompactVectorBuilder}; fn main() -> Result<(), Box> { // Build a vector storing integers within 3 bits each. - let mut builder = CompactVectorBuilder::new(3)?; - builder.extend([7, 2, 5])?; + let mut area = ByteArea::new()?; + let mut sections = area.sections(); + let mut builder = CompactVectorBuilder::with_capacity(3, 3, &mut sections)?; + builder.set_ints(0..3, [7, 2, 5])?; let cv = builder.freeze(); + let meta = cv.metadata(); + let bytes = area.freeze()?; + let view = CompactVector::from_bytes(meta, bytes.clone())?; - assert_eq!(cv.len(), 3); - assert_eq!(cv.get_int(0), Some(7)); - assert_eq!(cv.get_int(2), Some(5)); + assert_eq!(view.len(), 3); + assert_eq!(view.get_int(0), Some(7)); + assert_eq!(view.get_int(2), Some(5)); Ok(()) } diff --git a/examples/dacs_byte.rs b/examples/dacs_byte.rs new file mode 100644 index 0000000..120cbd1 --- /dev/null +++ b/examples/dacs_byte.rs @@ -0,0 +1,16 @@ +use anybytes::ByteArea; +use jerky::bit_vector::Rank9SelIndex; +use jerky::int_vectors::{Access, DacsByte}; + +fn main() -> Result<(), Box> { + let mut area = ByteArea::new()?; + let mut writer = area.sections(); + let seq = DacsByte::::from_slice(&[5, 0, 100000, 334], &mut writer)?; + let meta = seq.metadata(); + let bytes = area.freeze()?; + let view = DacsByte::::from_bytes(meta, bytes.clone())?; + + assert_eq!(view.access(2), Some(100000)); + assert_eq!(seq, view); + Ok(()) +} diff --git a/examples/wavelet_matrix.rs b/examples/wavelet_matrix.rs index 873a585..37550c6 100644 --- a/examples/wavelet_matrix.rs +++ b/examples/wavelet_matrix.rs @@ -1,16 +1,25 @@ +use anybytes::ByteArea; use jerky::bit_vector::Rank9SelIndex; -use jerky::char_sequences::WaveletMatrix; -use jerky::int_vectors::CompactVectorBuilder; +use jerky::char_sequences::{WaveletMatrix, WaveletMatrixBuilder}; fn main() -> Result<(), Box> { let text = "banana"; - let mut builder = CompactVectorBuilder::new(8)?; - builder.extend(text.chars().map(|c| c as usize))?; - let wm = WaveletMatrix::::new(builder.freeze())?; + let alph_size = ('n' as usize) + 1; + let mut area = ByteArea::new()?; + let mut sections = area.sections(); + let ints: Vec = text.chars().map(|c| c as usize).collect(); + let mut builder = WaveletMatrixBuilder::with_capacity(alph_size, ints.len(), &mut sections)?; + for (i, &x) in ints.iter().enumerate() { + builder.set_int(i, x)?; + } + let wm: WaveletMatrix = builder.freeze()?; + let meta = wm.metadata(); + let bytes = area.freeze()?; + let view = WaveletMatrix::::from_bytes(meta, bytes.clone())?; - assert_eq!(wm.len(), text.len()); - assert_eq!(wm.access(2), Some('n' as usize)); - assert_eq!(wm.rank(4, 'a' as usize), Some(2)); - assert_eq!(wm.select(1, 'n' as usize), Some(4)); + assert_eq!(view.len(), text.len()); + assert_eq!(view.access(2), Some('n' as usize)); + assert_eq!(view.rank(4, 'a' as usize), Some(2)); + assert_eq!(view.select(1, 'n' as usize), Some(4)); Ok(()) } diff --git a/src/bit_vector/mod.rs b/src/bit_vector/mod.rs index 3e27ece..c557835 100644 --- a/src/bit_vector/mod.rs +++ b/src/bit_vector/mod.rs @@ -49,8 +49,12 @@ //! # fn main() -> Result<(), Box> { //! use jerky::bit_vector::*; //! -//! let mut builder = BitVectorBuilder::new(); -//! builder.extend_bits([true, false, false, true]); +//! use anybytes::ByteArea; +//! let mut area = ByteArea::new()?; +//! let mut sections = area.sections(); +//! let mut builder = BitVectorBuilder::with_capacity(4, &mut sections)?; +//! builder.set_bit(0, true)?; +//! builder.set_bit(3, true)?; //! let bv = builder.freeze::(); //! //! assert_eq!(bv.num_bits(), 4); @@ -120,84 +124,71 @@ pub trait Select { } /// The number of bits in a machine word. -pub const WORD_LEN: usize = core::mem::size_of::() * 8; +pub const WORD_LEN: usize = core::mem::size_of::() * 8; -use anybytes::{Bytes, View}; +use anybytes::{area::SectionHandle, ByteArea, Bytes, Section, SectionWriter, View}; use anyhow::{anyhow, Result}; /// Builder that collects raw bits into a zero-copy [`BitVector`]. -#[derive(Debug, Default, Clone)] -pub struct BitVectorBuilder { - words: Vec, + +#[derive(Debug)] +pub struct BitVectorBuilder<'a> { + words: Section<'a, u64>, len: usize, } -impl BitVectorBuilder { - /// Creates an empty builder. - pub fn new() -> Self { - Self::default() +impl<'a> BitVectorBuilder<'a> { + /// Creates a builder storing `len` zero bits. + pub fn with_capacity(len: usize, writer: &mut SectionWriter<'a>) -> Result { + let num_words = (len + WORD_LEN - 1) / WORD_LEN; + let mut words = writer.reserve::(num_words)?; + words.as_mut_slice().fill(0); + Ok(Self { words, len }) } /// Creates a builder that stores `len` copies of `bit`. - pub fn from_bit(bit: bool, len: usize) -> Self { + pub fn from_bit(bit: bool, len: usize, writer: &mut SectionWriter<'a>) -> Result { if len == 0 { - return Self::default(); + return Self::with_capacity(0, writer); } - let word = if bit { usize::MAX } else { 0 }; + let word = if bit { u64::MAX } else { 0 }; let num_words = (len + WORD_LEN - 1) / WORD_LEN; - let mut words = vec![word; num_words]; + let mut words = writer.reserve::(num_words)?; + words.as_mut_slice().fill(word); let shift = len % WORD_LEN; if shift != 0 { - let mask = (1 << shift) - 1; - *words.last_mut().unwrap() &= mask; + let mask = (1u64 << shift) - 1; + *words.as_mut_slice().last_mut().unwrap() &= mask; } - Self { words, len } + Ok(Self { words, len }) } - /// Pushes a single bit. - pub fn push_bit(&mut self, bit: bool) { - let pos_in_word = self.len % WORD_LEN; - if pos_in_word == 0 { - self.words.push(bit as usize); - } else { - let cur = self.words.last_mut().unwrap(); - *cur |= (bit as usize) << pos_in_word; + /// Fills the entire builder with `bit`. + pub fn fill(&mut self, bit: bool) { + let word = if bit { u64::MAX } else { 0 }; + self.words.as_mut_slice().fill(word); + if bit { + let shift = self.len % WORD_LEN; + if shift != 0 { + let mask = (1u64 << shift) - 1; + if let Some(last) = self.words.as_mut_slice().last_mut() { + *last &= mask; + } + } } - self.len += 1; } - /// Pushes `len` bits from `bits` at the end. - /// - /// Bits outside the lowest `len` bits are truncated. - pub fn push_bits(&mut self, bits: usize, len: usize) -> Result<()> { - if WORD_LEN < len { + /// Returns the `pos`-th bit. + pub fn get_bit(&self, pos: usize) -> Result { + if self.len <= pos { return Err(anyhow!( - "len must be no greater than {WORD_LEN}, but got {len}." + "pos must be no greater than self.len()={}, but got {pos}.", + self.len )); } - if len == 0 { - return Ok(()); - } - - let mask = if len < WORD_LEN { - (1 << len) - 1 - } else { - usize::MAX - }; - let bits = bits & mask; - - let pos_in_word = self.len % WORD_LEN; - if pos_in_word == 0 { - self.words.push(bits); - } else { - let cur = self.words.last_mut().unwrap(); - *cur |= bits << pos_in_word; - if len > WORD_LEN - pos_in_word { - self.words.push(bits >> (WORD_LEN - pos_in_word)); - } - } - self.len += len; - Ok(()) + let word = pos / WORD_LEN; + let pos_in_word = pos % WORD_LEN; + Ok(((self.words[word] >> pos_in_word) & 1u64) == 1) } /// Sets the `pos`-th bit to `bit`. @@ -210,18 +201,103 @@ impl BitVectorBuilder { } let word = pos / WORD_LEN; let pos_in_word = pos % WORD_LEN; - self.words[word] &= !(1 << pos_in_word); - self.words[word] |= (bit as usize) << pos_in_word; + self.words[word] &= !(1u64 << pos_in_word); + self.words[word] |= (bit as u64) << pos_in_word; Ok(()) } - /// Extends the builder from an iterator of bits. - pub fn extend_bits>(&mut self, bits: I) { - bits.into_iter().for_each(|b| self.push_bit(b)); + /// Sets all bits in `range` to `bit`. + pub fn set_bits(&mut self, range: std::ops::Range, bit: bool) -> Result<()> { + if range.end > self.len { + return Err(anyhow!( + "range end must be no greater than self.len()={}, but got {}.", + self.len, + range.end + )); + } + if range.is_empty() { + return Ok(()); + } + let mut pos = range.start; + while pos < range.end { + let word = pos / WORD_LEN; + let pos_in_word = pos % WORD_LEN; + let remaining = range.end - pos; + let take = remaining.min(WORD_LEN - pos_in_word); + let mask = if take == WORD_LEN { + u64::MAX + } else { + ((1u64 << take) - 1) << pos_in_word + }; + if bit { + self.words[word] |= mask; + } else { + self.words[word] &= !mask; + } + pos += take; + } + Ok(()) + } + + /// Swaps bits at positions `a` and `b`. + pub fn swap_bits(&mut self, a: usize, b: usize) -> Result<()> { + if a >= self.len || b >= self.len { + return Err(anyhow!( + "positions must be less than self.len()={}, but got {a} and {b}.", + self.len + )); + } + if a == b { + return Ok(()); + } + let wa = a / WORD_LEN; + let wb = b / WORD_LEN; + let ba = a % WORD_LEN; + let bb = b % WORD_LEN; + let ma = 1u64 << ba; + let mb = 1u64 << bb; + let bit_a = (self.words[wa] & ma) != 0; + let bit_b = (self.words[wb] & mb) != 0; + if bit_a != bit_b { + self.words[wa] ^= ma; + self.words[wb] ^= mb; + } + Ok(()) + } + + /// Sets bits starting at `start` from the provided iterator. + /// + /// Bits are written sequentially from `start` until either the iterator is + /// exhausted or the builder capacity is reached. Returns the number of bits + /// written. The iterator is taken by mutable reference so that any + /// unconsumed items remain available to the caller. + pub fn set_bits_from_iter(&mut self, start: usize, bits: &mut I) -> Result + where + I: Iterator, + { + if start > self.len { + return Err(anyhow!( + "start must be no greater than self.len()={}, but got {}.", + self.len, + start + )); + } + let mut pos = start; + while pos < self.len { + match bits.next() { + Some(bit) => { + self.set_bit(pos, bit)?; + pos += 1; + } + None => break, + } + } + Ok(pos - start) } fn into_data(self) -> BitVectorData { - let words = Bytes::from_source(self.words).view::<[usize]>().unwrap(); + let words_bytes = self.words.freeze().expect("freeze section"); + let words = words_bytes.view::<[u64]>().unwrap(); BitVectorData { words, len: self.len, @@ -235,9 +311,9 @@ impl BitVectorBuilder { BitVector::new(data, index) } - /// Serializes the builder contents into a [`Bytes`] buffer. - pub fn into_bytes(self) -> (usize, Bytes) { - (self.len, Bytes::from_source(self.words)) + /// Returns a handle to the backing words section. + pub fn handle(&self) -> SectionHandle { + self.words.handle() } } @@ -245,7 +321,7 @@ impl BitVectorBuilder { #[derive(Debug, Clone, PartialEq, Eq)] pub struct BitVectorData { /// Underlying machine words storing bit data. - pub words: View<[usize]>, + pub words: View<[u64]>, /// Number of valid bits in `words`. pub len: usize, } @@ -253,7 +329,7 @@ pub struct BitVectorData { impl Default for BitVectorData { fn default() -> Self { Self { - words: Bytes::empty().view::<[usize]>().unwrap(), + words: Bytes::empty().view::<[u64]>().unwrap(), len: 0, } } @@ -262,14 +338,19 @@ impl Default for BitVectorData { impl BitVectorData { /// Creates bit vector data from a bit iterator. pub fn from_bits>(bits: I) -> Self { - let mut builder = BitVectorBuilder::new(); - builder.extend_bits(bits); + let vec: Vec = bits.into_iter().collect(); + let mut area = ByteArea::new().expect("byte area"); + let mut sections = area.sections(); + let mut builder = BitVectorBuilder::with_capacity(vec.len(), &mut sections).unwrap(); + for (i, b) in vec.into_iter().enumerate() { + builder.set_bit(i, b).unwrap(); + } builder.into_data() } /// Reconstructs the data from zero-copy [`Bytes`]. pub fn from_bytes(len: usize, bytes: Bytes) -> Result { - let words = bytes.view::<[usize]>().map_err(|e| anyhow::anyhow!(e))?; + let words = bytes.view::<[u64]>().map_err(|e| anyhow::anyhow!(e))?; Ok(Self { words, len }) } @@ -279,7 +360,7 @@ impl BitVectorData { } /// Returns the raw word slice. - pub fn words(&self) -> &[usize] { + pub fn words(&self) -> &[u64] { self.words.as_ref() } @@ -299,21 +380,16 @@ impl BitVectorData { let block = pos / WORD_LEN; let shift = pos % WORD_LEN; let mask = if len < WORD_LEN { - (1 << len) - 1 + (1u64 << len) - 1 } else { - usize::MAX + u64::MAX }; let bits = if shift + len <= WORD_LEN { (self.words[block] >> shift) & mask } else { (self.words[block] >> shift) | ((self.words[block + 1] << (WORD_LEN - shift)) & mask) }; - Some(bits) - } - - /// Serializes the data into a [`Bytes`] buffer. - pub fn to_bytes(&self) -> (usize, Bytes) { - (self.len, self.words.clone().bytes()) + Some(bits as usize) } } @@ -328,7 +404,7 @@ impl Access for BitVectorData { if pos < self.len { let block = pos / WORD_LEN; let shift = pos % WORD_LEN; - Some((self.words[block] >> shift) & 1 == 1) + Some((self.words[block] >> shift) & 1u64 == 1) } else { None } @@ -561,10 +637,12 @@ mod tests { #[test] fn builder_freeze() { - let mut builder = BitVectorBuilder::new(); - builder.extend_bits([true, false]); - builder.push_bits(0b10, 2).unwrap(); + let mut area = ByteArea::new().unwrap(); + let mut sections = area.sections(); + let mut builder = BitVectorBuilder::with_capacity(4, &mut sections).unwrap(); + builder.set_bit(0, true).unwrap(); builder.set_bit(1, true).unwrap(); + builder.set_bit(3, true).unwrap(); let bv: BitVector = builder.freeze::(); assert_eq!(bv.len(), 4); assert_eq!(bv.get_bits(0, 4), Some(0b1011)); @@ -572,11 +650,17 @@ mod tests { #[test] fn from_bytes_roundtrip() { - let mut builder = BitVectorBuilder::new(); - builder.extend_bits([true, false, true, true, false]); - let expected: BitVector = builder.clone().freeze::(); - let (len, bytes) = builder.into_bytes(); - + let mut area = ByteArea::new().unwrap(); + let mut sections = area.sections(); + let mut builder = BitVectorBuilder::with_capacity(5, &mut sections).unwrap(); + builder.set_bit(0, true).unwrap(); + builder.set_bit(2, true).unwrap(); + builder.set_bit(3, true).unwrap(); + let expected: BitVector = + BitVectorData::from_bits([true, false, true, true, false]).into(); + let bv: BitVector = builder.freeze::(); + let len = bv.data.len; + let bytes = bv.data.words.clone().bytes(); let data = BitVectorData::from_bytes(len, bytes).unwrap(); let other: BitVector = data.into(); assert_eq!(expected, other); @@ -593,22 +677,59 @@ mod tests { } #[test] - fn builder_push_bits_across_word() { - let mut builder = BitVectorBuilder::new(); - builder.extend_bits(core::iter::repeat(false).take(62)); - builder.push_bits(0b011111, 6).unwrap(); + fn builder_set_bits_across_word() { + let mut area = ByteArea::new().unwrap(); + let mut sections = area.sections(); + let mut builder = BitVectorBuilder::with_capacity(68, &mut sections).unwrap(); + builder.set_bits(0..68, false).unwrap(); + builder.set_bits(62..68, true).unwrap(); + builder.set_bit(67, false).unwrap(); let bv: BitVector = builder.freeze::(); assert_eq!(bv.data.get_bits(61, 7).unwrap(), 0b0111110); } #[test] fn builder_from_bit() { - let builder = BitVectorBuilder::from_bit(true, 5); + let mut area = ByteArea::new().unwrap(); + let mut sections = area.sections(); + let builder = BitVectorBuilder::from_bit(true, 5, &mut sections).unwrap(); let bv: BitVector = builder.freeze::(); assert_eq!(bv.len(), 5); assert_eq!(bv.num_ones(), 5); } + #[test] + fn builder_capacity_limit() { + let mut area = ByteArea::new().unwrap(); + let mut sections = area.sections(); + let mut builder = BitVectorBuilder::with_capacity(3, &mut sections).unwrap(); + assert!(builder.set_bit(3, true).is_err()); + } + + #[test] + fn builder_set_bits_from_iter() { + let mut area = ByteArea::new().unwrap(); + let mut sections = area.sections(); + let mut builder = BitVectorBuilder::with_capacity(4, &mut sections).unwrap(); + let mut iter = [true, false, true, true].iter().copied(); + let written = builder.set_bits_from_iter(0, &mut iter).unwrap(); + assert_eq!(written, 4); + let bv: BitVector = builder.freeze::(); + assert_eq!(bv.data.get_bits(0, 4).unwrap(), 0b1101); + } + + #[test] + fn builder_set_bits_from_iter_leaves_unconsumed() { + let mut area = ByteArea::new().unwrap(); + let mut sections = area.sections(); + let mut builder = BitVectorBuilder::with_capacity(2, &mut sections).unwrap(); + let mut iter = [true, false, true].iter().copied(); + let written = builder.set_bits_from_iter(0, &mut iter).unwrap(); + assert_eq!(written, 2); + let remaining: Vec = iter.collect(); + assert_eq!(remaining, vec![true]); + } + #[test] fn iter_collects() { let data = BitVectorData::from_bits([true, false, true]); diff --git a/src/bit_vector/rank9sel/inner.rs b/src/bit_vector/rank9sel/inner.rs index 42b20cc..48d6871 100644 --- a/src/bit_vector/rank9sel/inner.rs +++ b/src/bit_vector/rank9sel/inner.rs @@ -350,13 +350,13 @@ impl Rank9SelIndex { let mut cur_rank = self.block_rank(block); debug_assert!(cur_rank <= k); - let rank_in_block_parallel = (k - cur_rank) * broadword::ONES_STEP_9; - let sub_ranks = self.sub_block_ranks(block); - let sub_block_offset = (broadword::uleq_step_9(sub_ranks, rank_in_block_parallel) + let rank_in_block_parallel = (k - cur_rank) as u64 * broadword::ONES_STEP_9; + let sub_ranks = self.sub_block_ranks(block) as u64; + let sub_block_offset = ((broadword::uleq_step_9(sub_ranks, rank_in_block_parallel) .wrapping_mul(broadword::ONES_STEP_9) >> 54) - & 0x7; - cur_rank += (sub_ranks >> (7 - sub_block_offset).wrapping_mul(9)) & 0x1FF; + & 0x7) as usize; + cur_rank += ((sub_ranks >> (7 - sub_block_offset).wrapping_mul(9)) & 0x1FF) as usize; debug_assert!(cur_rank <= k); let word_offset = block_offset + sub_block_offset; @@ -423,13 +423,13 @@ impl Rank9SelIndex { let mut cur_rank = self.block_rank0(block); debug_assert!(cur_rank <= k); - let rank_in_block_parallel = (k - cur_rank) * broadword::ONES_STEP_9; - let sub_ranks = 64 * broadword::INV_COUNT_STEP_9 - self.sub_block_ranks(block); - let sub_block_offset = (broadword::uleq_step_9(sub_ranks, rank_in_block_parallel) + let rank_in_block_parallel = (k - cur_rank) as u64 * broadword::ONES_STEP_9; + let sub_ranks = 64u64 * broadword::INV_COUNT_STEP_9 - self.sub_block_ranks(block) as u64; + let sub_block_offset = ((broadword::uleq_step_9(sub_ranks, rank_in_block_parallel) .wrapping_mul(broadword::ONES_STEP_9) >> 54) - & 0x7; - cur_rank += (sub_ranks >> (7 - sub_block_offset).wrapping_mul(9)) & 0x1FF; + & 0x7) as usize; + cur_rank += ((sub_ranks >> (7 - sub_block_offset).wrapping_mul(9)) & 0x1FF) as usize; debug_assert!(cur_rank <= k); let word_offset = block_offset + sub_block_offset; @@ -493,29 +493,6 @@ impl Rank9SelIndex { select0_hints, }) } - - /// Serializes the index metadata and data into a [`Bytes`] buffer. - pub fn to_bytes(&self) -> Bytes { - let mut store: Vec = Vec::new(); - store.push(self.len); - store.push(self.block_rank_pairs.len()); - store.extend_from_slice(self.block_rank_pairs.as_ref()); - if let Some(ref v) = self.select1_hints { - store.push(1); - store.push(v.len()); - store.extend_from_slice(v.as_ref()); - } else { - store.push(0); - } - if let Some(ref v) = self.select0_hints { - store.push(1); - store.push(v.len()); - store.extend_from_slice(v.as_ref()); - } else { - store.push(0); - } - Bytes::from_source(store) - } } impl crate::bit_vector::BitVectorIndex @@ -553,15 +530,6 @@ impl crate::bit_vector::BitVectorIndex mod tests { use super::*; - #[test] - fn test_zero_copy_from_to_bytes() { - let data = BitVectorData::from_bits([false, true, true, false, true]); - let idx = Rank9SelIndex::::new(&data); - let bytes = idx.to_bytes(); - let other = Rank9SelIndex::::from_bytes(bytes).unwrap(); - assert_eq!(idx, other); - } - #[test] fn test_builder_new_equivalence() { let data = BitVectorData::from_bits([true, false, true, false]); diff --git a/src/broadword.rs b/src/broadword.rs index b31f793..624a668 100644 --- a/src/broadword.rs +++ b/src/broadword.rs @@ -5,39 +5,39 @@ #[cfg(feature = "intrinsics")] use crate::intrinsics; -pub(crate) const ONES_STEP_4: usize = 0x1111111111111111; -pub(crate) const ONES_STEP_8: usize = 0x0101010101010101; -pub(crate) const ONES_STEP_9: usize = +pub(crate) const ONES_STEP_4: u64 = 0x1111111111111111; +pub(crate) const ONES_STEP_8: u64 = 0x0101010101010101; +pub(crate) const ONES_STEP_9: u64 = (1 << 0) | (1 << 9) | (1 << 18) | (1 << 27) | (1 << 36) | (1 << 45) | (1 << 54); -pub(crate) const MSBS_STEP_8: usize = 0x80 * ONES_STEP_8; -pub(crate) const MSBS_STEP_9: usize = 0x100 * ONES_STEP_9; -pub(crate) const INV_COUNT_STEP_9: usize = +pub(crate) const MSBS_STEP_8: u64 = 0x80 * ONES_STEP_8; +pub(crate) const MSBS_STEP_9: u64 = 0x100 * ONES_STEP_9; +pub(crate) const INV_COUNT_STEP_9: u64 = (1 << 54) | (2 << 45) | (3 << 36) | (4 << 27) | (5 << 18) | (6 << 9) | 7; #[inline(always)] -pub(crate) const fn leq_step_8(x: usize, y: usize) -> usize { +pub(crate) const fn leq_step_8(x: u64, y: u64) -> u64 { ((((y | MSBS_STEP_8) - (x & !MSBS_STEP_8)) ^ (x ^ y)) & MSBS_STEP_8) >> 7 } #[inline(always)] -pub(crate) const fn uleq_step_8(x: usize, y: usize) -> usize { +pub(crate) const fn uleq_step_8(x: u64, y: u64) -> u64 { (((((y | MSBS_STEP_8) - (x & !MSBS_STEP_8)) ^ (x ^ y)) ^ (x & !y)) & MSBS_STEP_8) >> 7 } #[inline(always)] -pub(crate) const fn uleq_step_9(x: usize, y: usize) -> usize { +pub(crate) const fn uleq_step_9(x: u64, y: u64) -> u64 { (((((y | MSBS_STEP_9) - (x & !MSBS_STEP_9)) | (x ^ y)) ^ (x & !y)) & MSBS_STEP_9) >> 8 } #[inline(always)] -pub(crate) const fn byte_counts(mut x: usize) -> usize { +pub(crate) const fn byte_counts(mut x: u64) -> u64 { x = x - ((x & (0xa * ONES_STEP_4)) >> 1); x = (x & (3 * ONES_STEP_4)) + ((x >> 2) & (3 * ONES_STEP_4)); (x + (x >> 4)) & (0x0f * ONES_STEP_8) } #[inline(always)] -pub(crate) const fn bytes_sum(x: usize) -> usize { +pub(crate) const fn bytes_sum(x: u64) -> u64 { ONES_STEP_8.wrapping_mul(x) >> 56 } @@ -49,18 +49,18 @@ pub(crate) const fn bytes_sum(x: usize) -> usize { /// use jerky::broadword::popcount; /// /// assert_eq!(popcount(0), 0); -/// assert_eq!(popcount(usize::MAX), 64); +/// assert_eq!(popcount(u64::MAX), 64); /// assert_eq!(popcount(0b1010110011), 6); /// ``` #[inline(always)] -pub const fn popcount(x: usize) -> usize { +pub const fn popcount(x: u64) -> usize { #[cfg(not(feature = "intrinsics"))] { - bytes_sum(byte_counts(x)) + bytes_sum(byte_counts(x)) as usize } #[cfg(feature = "intrinsics")] { - intrinsics::popcount(x) + intrinsics::popcount(x as usize) } } @@ -78,12 +78,12 @@ pub const fn popcount(x: usize) -> usize { /// assert_eq!(select_in_word(0b1000011, 3), None); /// ``` #[inline(always)] -pub const fn select_in_word(x: usize, k: usize) -> Option { +pub const fn select_in_word(x: u64, k: usize) -> Option { if popcount(x) <= k { return None; } let byte_sums = ONES_STEP_8.wrapping_mul(byte_counts(x)); - let k_step_8 = k * ONES_STEP_8; + let k_step_8 = (k as u64) * ONES_STEP_8; let geq_k_step_8 = ((k_step_8 | MSBS_STEP_8) - byte_sums) & MSBS_STEP_8; let place = { #[cfg(feature = "intrinsics")] @@ -92,18 +92,18 @@ pub const fn select_in_word(x: usize, k: usize) -> Option { } #[cfg(not(feature = "intrinsics"))] { - ((geq_k_step_8 >> 7).wrapping_mul(ONES_STEP_8) >> 53) & !0x7 + (((geq_k_step_8 >> 7).wrapping_mul(ONES_STEP_8) >> 53) & !0x7) as usize } }; - let byte_rank = k - (((byte_sums << 8) >> place) & 0xFF); - let sel = place + SELECT_IN_BYTE[((x >> place) & 0xFF) | (byte_rank << 8)] as usize; + let byte_rank = k - (((byte_sums << 8) >> place) & 0xFF) as usize; + let sel = place + SELECT_IN_BYTE[(((x >> place) & 0xFF) as usize) | (byte_rank << 8)] as usize; Some(sel) } #[inline(always)] -pub(crate) fn bit_position(x: usize) -> usize { +pub(crate) fn bit_position(x: u64) -> usize { debug_assert!(popcount(x) == 1); - DEBRUIJN64_MAPPING[(DEBRUIJN64.wrapping_mul(x)) >> 58] as usize + DEBRUIJN64_MAPPING[(DEBRUIJN64.wrapping_mul(x) >> 58) as usize] as usize } /// Gets the least significant bit, returning [`None`] if `x == 0`. @@ -118,18 +118,18 @@ pub(crate) fn bit_position(x: usize) -> usize { /// ``` #[allow(clippy::missing_const_for_fn)] #[inline(always)] -pub fn lsb(x: usize) -> Option { +pub fn lsb(x: u64) -> Option { #[cfg(not(feature = "intrinsics"))] { if x == 0 { None } else { - Some(bit_position(x & usize::MAX.wrapping_mul(x))) + Some(bit_position(x & u64::MAX.wrapping_mul(x))) } } #[cfg(feature = "intrinsics")] { - intrinsics::bsf64(x) + intrinsics::bsf64(x as usize) } } @@ -145,7 +145,7 @@ pub fn lsb(x: usize) -> Option { /// ``` #[allow(clippy::missing_const_for_fn)] #[inline(always)] -pub fn msb(x: usize) -> Option { +pub fn msb(x: u64) -> Option { #[cfg(not(feature = "intrinsics"))] { if x == 0 { @@ -165,7 +165,7 @@ pub fn msb(x: usize) -> Option { } #[cfg(feature = "intrinsics")] { - intrinsics::bsr64(x) + intrinsics::bsr64(x as usize) } } @@ -242,7 +242,7 @@ const DEBRUIJN64_MAPPING: [u8; 64] = [ 45, 25, 31, 35, 16, 9, 12, 44, 24, 15, 8, 23, 7, 6, 5, ]; -const DEBRUIJN64: usize = 0x07EDD5E59A4E28C2; +const DEBRUIJN64: u64 = 0x07EDD5E59A4E28C2; #[cfg(test)] mod tests { @@ -250,7 +250,7 @@ mod tests { #[test] fn test_select_in_word() { - let x: usize = 0b0000010011000011000011100100000000010000100010000010001100010011; + let x: u64 = 0b0000010011000011000011100100000000010000100010000010001100010011; let sels = [ 0, 1, 4, 8, 9, 13, 19, 23, 28, 38, 41, 42, 43, 48, 49, 54, 55, 58, ]; diff --git a/src/char_sequences/mod.rs b/src/char_sequences/mod.rs index 50bb015..c924eed 100644 --- a/src/char_sequences/mod.rs +++ b/src/char_sequences/mod.rs @@ -34,4 +34,4 @@ //! For simplicity, the above table assumes constant-time and linear-space implementation. pub mod wavelet_matrix; -pub use wavelet_matrix::WaveletMatrix; +pub use wavelet_matrix::{WaveletMatrix, WaveletMatrixBuilder}; diff --git a/src/char_sequences/wavelet_matrix.rs b/src/char_sequences/wavelet_matrix.rs index 9a8fe9a..2ca540b 100644 --- a/src/char_sequences/wavelet_matrix.rs +++ b/src/char_sequences/wavelet_matrix.rs @@ -2,16 +2,15 @@ //! supporting some queries such as ranking, selection, and intersection. #![cfg(target_pointer_width = "64")] -use std::ops::Range; +use std::{iter::ExactSizeIterator, ops::Range}; -use anybytes::Bytes; +use anybytes::{area::SectionHandle, ByteArea, Bytes, Section, SectionWriter}; use anyhow::{anyhow, Result}; use crate::bit_vector::{ Access, BitVector, BitVectorBuilder, BitVectorData, BitVectorIndex, NumBits, Rank, Select, - WORD_LEN, }; -use crate::int_vectors::{CompactVector, CompactVectorBuilder}; +use crate::serialization::Serializable; use crate::utils; /// Time- and space-efficient data structure for a sequence of integers, @@ -26,20 +25,23 @@ use crate::utils; /// /// ``` /// # fn main() -> Result<(), Box> { -/// use jerky::bit_vector::{rank9sel::inner::Rank9SelIndex, BitVector}; +/// use jerky::bit_vector::Rank9SelIndex; /// use jerky::char_sequences::WaveletMatrix; -/// use jerky::int_vectors::{CompactVector, CompactVectorBuilder}; +/// use anybytes::ByteArea; /// /// let text = "banana"; -/// let len = text.chars().count(); -/// -/// // It accepts an integer representable in 8 bits. -/// let mut builder = CompactVectorBuilder::new(8)?; -/// builder.extend(text.chars().map(|c| c as usize))?; -/// let wm = WaveletMatrix::::new(builder.freeze())?; +/// let alph_size = ('n' as usize) + 1; +/// let len = text.len(); +/// let mut area = ByteArea::new()?; +/// let mut sections = area.sections(); +/// let wm = WaveletMatrix::::from_iter( +/// alph_size, +/// text.bytes().map(|b| b as usize), +/// &mut sections, +/// )?; /// /// assert_eq!(wm.len(), len); -/// assert_eq!(wm.alph_size(), 'n' as usize + 1); +/// assert_eq!(wm.alph_size(), alph_size); /// assert_eq!(wm.alph_width(), 7); /// /// assert_eq!(wm.access(2), Some('n' as usize)); @@ -56,14 +58,26 @@ use crate::utils; /// # References /// /// - F. Claude, and G. Navarro, "The Wavelet Matrix," In SPIRE 2012. -#[derive(Default, Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone)] pub struct WaveletMatrix { layers: Vec>, alph_size: usize, + layers_handle: SectionHandle>, +} + +impl PartialEq for WaveletMatrix { + fn eq(&self, other: &Self) -> bool { + self.layers == other.layers + && self.alph_size == other.alph_size + && self.layers_handle.offset == other.layers_handle.offset + && self.layers_handle.len == other.layers_handle.len + } } +impl Eq for WaveletMatrix {} + /// Metadata describing the serialized form of a [`WaveletMatrix`]. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy)] pub struct WaveletMatrixMeta { /// Maximum value + 1 stored in the matrix. pub alph_size: usize, @@ -71,76 +85,224 @@ pub struct WaveletMatrixMeta { pub alph_width: usize, /// Number of integers stored. pub len: usize, + /// Handle to a slice of per-layer [`SectionHandle`]s stored in the byte arena. + pub layers: SectionHandle>, } -impl WaveletMatrix -where - I: BitVectorIndex, -{ - /// Creates a new instance from an input sequence `seq`. - /// - /// # Errors - /// - /// An error is returned if - /// - /// - `seq` is empty. - pub fn new(seq: CompactVector) -> Result { - if seq.is_empty() { +/// Builder for [`WaveletMatrix`]. +pub struct WaveletMatrixBuilder<'a> { + alph_size: usize, + len: usize, + alph_width: usize, + layers: Vec>, + handles: Section<'a, SectionHandle>, +} + +impl<'a> WaveletMatrixBuilder<'a> { + /// Creates a builder for a sequence of `len` integers in `0..alph_size`. + pub fn with_capacity( + alph_size: usize, + len: usize, + writer: &mut SectionWriter<'a>, + ) -> Result { + if len == 0 { return Err(anyhow!("seq must not be empty.")); } - - let alph_size = seq.iter().max().unwrap() + 1; let alph_width = utils::needed_bits(alph_size); + let mut handles = writer.reserve::>(alph_width)?; + let mut layers = Vec::with_capacity(alph_width); + for idx in 0..alph_width { + let builder = BitVectorBuilder::with_capacity(len, writer)?; + handles[idx] = builder.handle(); + layers.push(builder); + } + Ok(Self { + alph_size, + len, + alph_width, + layers, + handles, + }) + } - let mut zeros = seq; - let mut ones = CompactVector::new(alph_width)?.freeze(); - let mut layers = vec![]; - - for depth in 0..alph_width { - let mut next_zeros = CompactVectorBuilder::new(alph_width).unwrap(); - let mut next_ones = CompactVectorBuilder::new(alph_width).unwrap(); - let mut bv = BitVectorBuilder::new(); - Self::filter( - &zeros, - alph_width - depth - 1, - &mut next_zeros, - &mut next_ones, - &mut bv, - ); - Self::filter( - &ones, - alph_width - depth - 1, - &mut next_zeros, - &mut next_ones, - &mut bv, - ); - zeros = next_zeros.freeze(); - ones = next_ones.freeze(); - let bits = bv.freeze::(); - layers.push(bits); + /// Sets the `pos`-th integer to `val`. + pub fn set_int(&mut self, pos: usize, val: usize) -> Result<()> { + if self.len <= pos { + return Err(anyhow!( + "pos must be no greater than self.len()={}, but got {pos}.", + self.len + )); + } + if val >= self.alph_size { + return Err(anyhow!("value {} out of range 0..{}", val, self.alph_size)); } + for (layer_idx, layer) in self.layers.iter_mut().enumerate() { + let shift = self.alph_width - 1 - layer_idx; + let bit = ((val >> shift) & 1) == 1; + layer.set_bit(pos, bit)?; + } + Ok(()) + } - Ok(Self { layers, alph_size }) + /// Writes integers from `ints` starting at `start`. + /// + /// Returns the number of integers written. + pub fn set_ints_from_iter(&mut self, start: usize, ints: &mut I) -> Result + where + I: Iterator, + { + if start > self.len { + return Err(anyhow!( + "start must be no greater than self.len()={}, but got {}.", + self.len, + start + )); + } + let mut pos = start; + while pos < self.len { + match ints.next() { + Some(x) => { + self.set_int(pos, x)?; + pos += 1; + } + None => break, + } + } + Ok(pos - start) } - fn filter( - seq: &CompactVector, - shift: usize, - next_zeros: &mut CompactVectorBuilder, - next_ones: &mut CompactVectorBuilder, - bv: &mut BitVectorBuilder, - ) { - for val in seq.iter() { - let bit = ((val >> shift) & 1) == 1; - bv.push_bit(bit); - if bit { - next_ones.push_int(val).unwrap(); - } else { - next_zeros.push_int(val).unwrap(); + /// Finalizes the builder into a [`WaveletMatrix`]. + /// + /// Layers are frozen from most significant to least significant bit. + /// After freezing the current layer, the permutation induced by its bits is + /// applied in place to all remaining (deeper) layers using cycle + /// rotations. A single scratch `visited` bitmap tracks processed + /// positions, ensuring $`O(n)`$ extra bits overall. + pub fn freeze(self) -> Result> { + // Builders for yet-unfrozen layers. Reverse so popping yields the next + // layer without shifting the entire vector each iteration. + let mut remaining = self.layers; + remaining.reverse(); + let mut layers = Vec::with_capacity(self.alph_width); + let mut scratch = ByteArea::new()?; + let mut scratch_sections = scratch.sections(); + let mut visited = BitVectorBuilder::with_capacity(self.len, &mut scratch_sections)?; + let handles = self.handles; + + for _ in 0..self.alph_width { + // Freeze the next layer and obtain its index for rank/select queries. + let builder = remaining.pop().expect("layer available"); + let layer = builder.freeze::(); + let zeros = layer.num_zeros(); + + if !remaining.is_empty() { + // Apply the permutation induced by this layer to all deeper + // levels. Only the minority side needs to start cycles. + visited.fill(false); + let ones = self.len - zeros; + let iterate_zeros = zeros <= ones; + let count = if iterate_zeros { zeros } else { ones }; + + for t in 0..count { + let start = if iterate_zeros { + layer.select0(t).expect("select0 in range") + } else { + layer.select1(t).expect("select1 in range") + }; + if visited.get_bit(start)? { + continue; + } + rotate_cycle_over_lower_levels( + &layer, + zeros, + start, + &mut remaining, + &mut visited, + )?; + } } + + layers.push(layer); } + + let layers_handle = handles.handle(); + handles.freeze()?; + Ok(WaveletMatrix { + layers, + alph_size: self.alph_size, + layers_handle, + }) } +} +/// Rotates a permutation cycle for all deeper layers during freezing. +/// +/// The cycle starts at `start` and follows the permutation induced by the +/// already-frozen layer `layer`. Bits on levels below `layer` are moved in +/// place without any additional buffers. +fn rotate_cycle_over_lower_levels( + layer: &BitVector, + zeros: usize, + start: usize, + lower: &mut [BitVectorBuilder<'_>], + visited: &mut BitVectorBuilder<'_>, +) -> Result<()> { + debug_assert!(lower.len() <= usize::BITS as usize); + let mut carry: usize = 0; + for (offset, b) in lower.iter().enumerate() { + if b.get_bit(start)? { + carry |= 1usize << offset; + } + } + let mut p = start; + loop { + visited.set_bit(p, true)?; + let bit = layer.access(p).expect("access within bounds"); + let q = if !bit { + layer.rank0(p).expect("rank0 within bounds") + } else { + zeros + layer.rank1(p).expect("rank1 within bounds") + }; + for (offset, b) in lower.iter_mut().enumerate() { + let tmp = b.get_bit(q)?; + let bit = (carry >> offset) & 1 == 1; + b.set_bit(q, bit)?; + carry = (carry & !(1usize << offset)) | ((tmp as usize) << offset); + } + p = q; + if visited.get_bit(p)? { + break; + } + } + Ok(()) +} + +impl WaveletMatrix +where + I: BitVectorIndex, +{ + /// Builds a [`WaveletMatrix`] from a single exact-size iterator. + pub fn from_iter<'a, J>( + alph_size: usize, + ints: J, + writer: &mut SectionWriter<'a>, + ) -> Result + where + J: ExactSizeIterator, + { + let len = ints.len(); + let mut builder = WaveletMatrixBuilder::with_capacity(alph_size, len, writer)?; + for (i, x) in ints.enumerate() { + builder.set_int(i, x)?; + } + builder.freeze() + } +} + +impl WaveletMatrix +where + I: BitVectorIndex, +{ /// Returns the `pos`-th integer, or [`None`] if `self.len() <= pos`. /// /// # Arguments @@ -155,13 +317,19 @@ where /// /// ``` /// # fn main() -> Result<(), Box> { - /// use jerky::bit_vector::{rank9sel::inner::Rank9SelIndex, BitVector}; + /// use jerky::bit_vector::Rank9SelIndex; /// use jerky::char_sequences::WaveletMatrix; - /// use jerky::int_vectors::{CompactVector, CompactVectorBuilder}; - /// - /// let mut builder = CompactVectorBuilder::new(8)?; - /// builder.extend("banana".chars().map(|c| c as usize))?; - /// let wm = WaveletMatrix::::new(builder.freeze())?; + /// use anybytes::ByteArea; + /// + /// let text = "banana"; + /// let alph_size = ('n' as usize) + 1; + /// let mut area = ByteArea::new()?; + /// let mut sections = area.sections(); + /// let wm = WaveletMatrix::::from_iter( + /// alph_size, + /// text.bytes().map(|b| b as usize), + /// &mut sections, + /// )?; /// /// assert_eq!(wm.access(2), Some('n' as usize)); /// assert_eq!(wm.access(5), Some('a' as usize)); @@ -205,13 +373,19 @@ where /// /// ``` /// # fn main() -> Result<(), Box> { - /// use jerky::bit_vector::{rank9sel::inner::Rank9SelIndex, BitVector}; + /// use jerky::bit_vector::Rank9SelIndex; /// use jerky::char_sequences::WaveletMatrix; - /// use jerky::int_vectors::{CompactVector, CompactVectorBuilder}; - /// - /// let mut builder = CompactVectorBuilder::new(8)?; - /// builder.extend("banana".chars().map(|c| c as usize))?; - /// let wm = WaveletMatrix::::new(builder.freeze())?; + /// use anybytes::ByteArea; + /// + /// let text = "banana"; + /// let alph_size = ('n' as usize) + 1; + /// let mut area = ByteArea::new()?; + /// let mut sections = area.sections(); + /// let wm = WaveletMatrix::::from_iter( + /// alph_size, + /// text.bytes().map(|b| b as usize), + /// &mut sections, + /// )?; /// /// assert_eq!(wm.rank(3, 'a' as usize), Some(1)); /// assert_eq!(wm.rank(5, 'c' as usize), Some(0)); @@ -240,13 +414,19 @@ where /// /// ``` /// # fn main() -> Result<(), Box> { - /// use jerky::bit_vector::{rank9sel::inner::Rank9SelIndex, BitVector}; + /// use jerky::bit_vector::Rank9SelIndex; /// use jerky::char_sequences::WaveletMatrix; - /// use jerky::int_vectors::{CompactVector, CompactVectorBuilder}; - /// - /// let mut builder = CompactVectorBuilder::new(8)?; - /// builder.extend("banana".chars().map(|c| c as usize))?; - /// let wm = WaveletMatrix::::new(builder.freeze())?; + /// use anybytes::ByteArea; + /// + /// let text = "banana"; + /// let alph_size = ('n' as usize) + 1; + /// let mut area = ByteArea::new()?; + /// let mut sections = area.sections(); + /// let wm = WaveletMatrix::::from_iter( + /// alph_size, + /// text.bytes().map(|b| b as usize), + /// &mut sections, + /// )?; /// /// assert_eq!(wm.rank_range(1..4, 'a' as usize), Some(2)); /// assert_eq!(wm.rank_range(2..4, 'c' as usize), Some(0)); @@ -296,13 +476,19 @@ where /// /// ``` /// # fn main() -> Result<(), Box> { - /// use jerky::bit_vector::{rank9sel::inner::Rank9SelIndex, BitVector}; + /// use jerky::bit_vector::Rank9SelIndex; /// use jerky::char_sequences::WaveletMatrix; - /// use jerky::int_vectors::{CompactVector, CompactVectorBuilder}; - /// - /// let mut builder = CompactVectorBuilder::new(8)?; - /// builder.extend("banana".chars().map(|c| c as usize))?; - /// let wm = WaveletMatrix::::new(builder.freeze())?; + /// use anybytes::ByteArea; + /// + /// let text = "banana"; + /// let alph_size = ('n' as usize) + 1; + /// let mut area = ByteArea::new()?; + /// let mut sections = area.sections(); + /// let wm = WaveletMatrix::::from_iter( + /// alph_size, + /// text.bytes().map(|b| b as usize), + /// &mut sections, + /// )?; /// /// assert_eq!(wm.select(1, 'a' as usize), Some(3)); /// assert_eq!(wm.select(0, 'c' as usize), None); @@ -357,13 +543,19 @@ where /// /// ``` /// # fn main() -> Result<(), Box> { - /// use jerky::bit_vector::{rank9sel::inner::Rank9SelIndex, BitVector}; + /// use jerky::bit_vector::Rank9SelIndex; /// use jerky::char_sequences::WaveletMatrix; - /// use jerky::int_vectors::{CompactVector, CompactVectorBuilder}; - /// - /// let mut builder = CompactVectorBuilder::new(8)?; - /// builder.extend("banana".chars().map(|c| c as usize))?; - /// let wm = WaveletMatrix::::new(builder.freeze())?; + /// use anybytes::ByteArea; + /// + /// let text = "banana"; + /// let alph_size = ('n' as usize) + 1; + /// let mut area = ByteArea::new()?; + /// let mut sections = area.sections(); + /// let wm = WaveletMatrix::::from_iter( + /// alph_size, + /// text.bytes().map(|b| b as usize), + /// &mut sections, + /// )?; /// /// assert_eq!(wm.quantile(1..4, 0), Some('a' as usize)); // The 0th in "ana" should be "a" /// assert_eq!(wm.quantile(1..4, 1), Some('a' as usize)); // The 1st in "ana" should be "a" @@ -425,13 +617,19 @@ where /// /// ``` /// # fn main() -> Result<(), Box> { - /// use jerky::bit_vector::{rank9sel::inner::Rank9SelIndex, BitVector}; + /// use jerky::bit_vector::Rank9SelIndex; /// use jerky::char_sequences::WaveletMatrix; - /// use jerky::int_vectors::{CompactVector, CompactVectorBuilder}; - /// - /// let mut builder = CompactVectorBuilder::new(8)?; - /// builder.extend("banana".chars().map(|c| c as usize))?; - /// let wm = WaveletMatrix::::new(builder.freeze())?; + /// use anybytes::ByteArea; + /// + /// let text = "banana"; + /// let alph_size = ('n' as usize) + 1; + /// let mut area = ByteArea::new()?; + /// let mut sections = area.sections(); + /// let wm = WaveletMatrix::::from_iter( + /// alph_size, + /// text.bytes().map(|b| b as usize), + /// &mut sections, + /// )?; /// /// // Intersections among "ana", "na", and "ba". /// assert_eq!( @@ -531,13 +729,19 @@ where /// /// ``` /// # fn main() -> Result<(), Box> { - /// use jerky::bit_vector::{rank9sel::inner::Rank9SelIndex, BitVector}; + /// use jerky::bit_vector::Rank9SelIndex; /// use jerky::char_sequences::WaveletMatrix; - /// use jerky::int_vectors::{CompactVector, CompactVectorBuilder}; - /// - /// let mut builder = CompactVectorBuilder::new(8)?; - /// builder.extend("ban".chars().map(|c| c as usize))?; - /// let wm = WaveletMatrix::::new(builder.freeze())?; + /// use anybytes::ByteArea; + /// + /// let text = "ban"; + /// let alph_size = ('n' as usize) + 1; + /// let mut area = ByteArea::new()?; + /// let mut sections = area.sections(); + /// let wm = WaveletMatrix::::from_iter( + /// alph_size, + /// text.bytes().map(|b| b as usize), + /// &mut sections, + /// )?; /// /// let mut it = wm.iter(); /// assert_eq!(it.next(), Some('b' as usize)); @@ -575,28 +779,34 @@ where self.layers.len() } - /// Serializes the sequence into a [`Bytes`] buffer along with its metadata. - pub fn to_bytes(&self) -> (WaveletMatrixMeta, Bytes) { - let mut store: Vec = Vec::new(); - for layer in &self.layers { - store.extend_from_slice(layer.data.words()); - } - let meta = WaveletMatrixMeta { + /// Returns metadata describing this matrix. + pub fn metadata(&self) -> WaveletMatrixMeta { + ::metadata(self) + } + + /// Reconstructs the sequence from metadata and a zero-copy [`Bytes`] buffer. + pub fn from_bytes(meta: WaveletMatrixMeta, bytes: Bytes) -> Result { + ::from_bytes(meta, bytes) + } +} + +impl Serializable for WaveletMatrix { + type Meta = WaveletMatrixMeta; + + fn metadata(&self) -> Self::Meta { + WaveletMatrixMeta { alph_size: self.alph_size, alph_width: self.alph_width(), len: self.len(), - }; - (meta, Bytes::from_source(store)) + layers: self.layers_handle, + } } - /// Reconstructs the sequence from metadata and a zero-copy [`Bytes`] buffer. - pub fn from_bytes(meta: WaveletMatrixMeta, mut bytes: Bytes) -> Result { + fn from_bytes(meta: Self::Meta, bytes: Bytes) -> Result { + let handles_view = meta.layers.view(&bytes).map_err(anyhow::Error::from)?; let mut layers = Vec::with_capacity(meta.alph_width); - let num_words = (meta.len + WORD_LEN - 1) / WORD_LEN; - for _ in 0..meta.alph_width { - let words = bytes - .view_prefix_with_elems::<[usize]>(num_words) - .map_err(|e| anyhow!(e))?; + for h in handles_view.as_ref() { + let words = h.view(&bytes).map_err(anyhow::Error::from)?; let data = BitVectorData { words, len: meta.len, @@ -607,6 +817,7 @@ where Ok(Self { layers, alph_size: meta.alph_size, + layers_handle: meta.layers, }) } } @@ -652,10 +863,13 @@ mod test { use super::*; use crate::bit_vector::rank9sel::inner::Rank9SelIndex; + use std::iter; #[test] fn test_empty_seq() { - let e = WaveletMatrix::::new(CompactVector::new(1).unwrap().freeze()); + let mut area = ByteArea::new().unwrap(); + let mut sections = area.sections(); + let e = WaveletMatrix::::from_iter(1, iter::empty(), &mut sections); assert_eq!( e.err().map(|x| x.to_string()), Some("seq must not be empty.".to_string()) @@ -666,12 +880,18 @@ mod test { fn test_navarro_book() { // This test example is from G. Navarro's "Compact Data Structures" P130 let text = "tobeornottobethatisthequestion"; - let len = text.chars().count(); - - let mut builder = CompactVectorBuilder::new(8).unwrap(); - builder.extend(text.chars().map(|c| c as usize)).unwrap(); - let seq = builder.freeze(); - let wm = WaveletMatrix::::new(seq).unwrap(); + let len = text.len(); + + let mut area = ByteArea::new().unwrap(); + let mut sections = area.sections(); + let alph_size = ('u' as usize) + 1; + let ints: Vec = text.bytes().map(|b| b as usize).collect(); + let wm = WaveletMatrix::::from_iter( + alph_size, + ints.iter().copied(), + &mut sections, + ) + .unwrap(); assert_eq!(wm.len(), len); assert_eq!(wm.alph_size(), ('u' as usize) + 1); @@ -690,14 +910,38 @@ mod test { assert_eq!(wm.intersect(&ranges, 1), Some(vec!['o' as usize])); } + #[test] + fn builder_sets_ints() { + let text = "banana"; + let alph_size = ('n' as usize) + 1; + let ints: Vec = text.bytes().map(|b| b as usize).collect(); + let mut area = ByteArea::new().unwrap(); + let mut sections = area.sections(); + let mut builder = + WaveletMatrixBuilder::with_capacity(alph_size, ints.len(), &mut sections).unwrap(); + for (i, &x) in ints.iter().enumerate() { + builder.set_int(i, x).unwrap(); + } + let wm = builder.freeze::().unwrap(); + assert_eq!(wm.len(), text.len()); + assert_eq!(wm.access(2), Some('n' as usize)); + } + #[test] fn from_bytes_roundtrip() { - let mut builder = CompactVectorBuilder::new(8).unwrap(); - builder - .extend("banana".chars().map(|c| c as usize)) - .unwrap(); - let wm = WaveletMatrix::::new(builder.freeze()).unwrap(); - let (meta, bytes) = wm.to_bytes(); + let mut area = ByteArea::new().unwrap(); + let mut sections = area.sections(); + let text = "banana"; + let alph_size = ('n' as usize) + 1; + let ints: Vec = text.bytes().map(|b| b as usize).collect(); + let wm = WaveletMatrix::::from_iter( + alph_size, + ints.iter().copied(), + &mut sections, + ) + .unwrap(); + let bytes = area.freeze().unwrap(); + let meta = wm.metadata(); let other = WaveletMatrix::::from_bytes(meta, bytes).unwrap(); assert_eq!(wm, other); } diff --git a/src/int_vectors/compact_vector.rs b/src/int_vectors/compact_vector.rs index 4524d24..75c3e37 100644 --- a/src/int_vectors/compact_vector.rs +++ b/src/int_vectors/compact_vector.rs @@ -3,83 +3,65 @@ use anyhow::{anyhow, Result}; use num_traits::ToPrimitive; +use std::iter::ExactSizeIterator; use crate::bit_vector::BitVectorBuilder; use crate::bit_vector::{BitVector, BitVectorData, NoIndex}; use crate::int_vectors::prelude::*; +use crate::serialization::Serializable; use crate::utils; -use anybytes::Bytes; +use anybytes::{area::SectionHandle, ByteArea, Bytes, SectionWriter}; /// Mutable builder for [`CompactVector`]. /// -/// This structure collects integers using [`push_int`], [`set_int`], or -/// [`extend`] and finally [`freeze`]s into an immutable [`CompactVector`]. +/// This structure collects integers using [`set_int`] or [`set_ints`] +/// and finally [`freeze`]s into an immutable [`CompactVector`]. /// /// # Examples /// ``` /// # fn main() -> Result<(), Box> { /// use jerky::int_vectors::CompactVectorBuilder; -/// let mut builder = CompactVectorBuilder::new(3)?; -/// builder.push_int(1)?; -/// builder.extend([2, 5])?; +/// use anybytes::ByteArea; +/// let mut area = ByteArea::new()?; +/// let mut sections = area.sections(); +/// let mut builder = CompactVectorBuilder::with_capacity(3, 3, &mut sections)?; +/// builder.set_ints(0..3, [1, 2, 5])?; /// let cv = builder.freeze(); /// assert_eq!(cv.get_int(1), Some(2)); /// # Ok(()) /// # } /// ``` -#[derive(Debug, Default, Clone)] -pub struct CompactVectorBuilder { - chunks: BitVectorBuilder, +#[derive(Debug)] +pub struct CompactVectorBuilder<'a> { + chunks: BitVectorBuilder<'a>, len: usize, width: usize, + capacity: usize, } -impl CompactVectorBuilder { - /// Creates a new empty builder storing integers within `width` bits each. +impl<'a> CompactVectorBuilder<'a> { + /// Creates a new builder reserving space for `capa` integers. /// - /// # Arguments - /// - /// * `width` - Number of bits used to store each integer. - /// - /// # Errors - /// - /// Returns an error if `width` is outside `1..=64`. - pub fn new(width: usize) -> Result { + /// Pushing more than `capa` integers will return an error. + pub fn with_capacity( + capa: usize, + width: usize, + writer: &mut SectionWriter<'a>, + ) -> Result { if !(1..=64).contains(&width) { return Err(anyhow!("width must be in 1..=64, but got {width}.")); } + let bits = capa + .checked_mul(width) + .ok_or_else(|| anyhow!("capa * width overflowed"))?; Ok(Self { - chunks: BitVectorBuilder::new(), + chunks: BitVectorBuilder::with_capacity(bits, writer)?, len: 0, width, + capacity: capa, }) } - /// Creates a new builder reserving space for at least `capa` integers. - /// - /// Currently the reservation is ignored as the builder grows - /// automatically. - pub fn with_capacity(_capa: usize, width: usize) -> Result { - Self::new(width) - } - - /// Pushes integer `val` at the end. - /// - /// # Errors - /// - /// Returns an error if `val` cannot be represented in `self.width()` bits. - pub fn push_int(&mut self, val: usize) -> Result<()> { - if self.width != 64 && val >> self.width != 0 { - return Err(anyhow!( - "val must fit in self.width()={} bits, but got {val}.", - self.width - )); - } - self.chunks.push_bits(val, self.width)?; - self.len += 1; - Ok(()) - } - /// Sets the `pos`-th integer to `val`. /// /// # Errors @@ -87,10 +69,10 @@ impl CompactVectorBuilder { /// Returns an error if `pos` is out of bounds or if `val` does not fit in /// `self.width()` bits. pub fn set_int(&mut self, pos: usize, val: usize) -> Result<()> { - if self.len <= pos { + if self.capacity <= pos { return Err(anyhow!( - "pos must be no greater than self.len()={}, but got {pos}.", - self.len + "pos must be no greater than self.capacity()={}, but got {pos}.", + self.capacity )); } if self.width != 64 && val >> self.width != 0 { @@ -103,20 +85,36 @@ impl CompactVectorBuilder { let bit = ((val >> i) & 1) == 1; self.chunks.set_bit(pos * self.width + i, bit)?; } + if self.len <= pos { + self.len = pos + 1; + } Ok(()) } - /// Appends integers at the end. + /// Sets integers in `range` from the provided values. /// - /// # Errors - /// - /// Returns an error if any value does not fit in `self.width()` bits. - pub fn extend(&mut self, vals: I) -> Result<()> + /// The number of integers in `vals` must match `range.len()`. + pub fn set_ints(&mut self, range: std::ops::Range, vals: I) -> Result<()> where I: IntoIterator, { - for x in vals { - self.push_int(x)?; + if range.end > self.capacity { + return Err(anyhow!( + "range end must be no greater than self.capacity()={}, but got {}.", + self.capacity, + range.end + )); + } + let mut pos = range.start; + for x in vals.into_iter() { + if pos >= range.end { + return Err(anyhow!("too many values for the specified range")); + } + self.set_int(pos, x)?; + pos += 1; + } + if pos != range.end { + return Err(anyhow!("not enough values for the specified range")); } Ok(()) } @@ -129,17 +127,22 @@ impl CompactVectorBuilder { /// ``` /// # fn main() -> Result<(), Box> { /// use jerky::int_vectors::CompactVectorBuilder; - /// let cv = CompactVectorBuilder::new(2)?.freeze(); + /// use anybytes::ByteArea; + /// let mut area = ByteArea::new()?; + /// let mut sections = area.sections(); + /// let cv = CompactVectorBuilder::with_capacity(0, 2, &mut sections)?.freeze(); /// assert!(cv.is_empty()); /// # Ok(()) /// # } /// ``` pub fn freeze(self) -> CompactVector { + let handle = self.chunks.handle(); let chunks: BitVector = self.chunks.freeze::(); CompactVector { chunks, len: self.len, width: self.width, + handle, } } } @@ -155,11 +158,13 @@ impl CompactVectorBuilder { /// ``` /// # fn main() -> Result<(), Box> { /// use jerky::int_vectors::{CompactVector, CompactVectorBuilder}; +/// use anybytes::ByteArea; /// /// // Can store integers within 3 bits each. -/// let mut builder = CompactVectorBuilder::new(3)?; -/// builder.push_int(7)?; -/// builder.push_int(2)?; +/// let mut area = ByteArea::new()?; +/// let mut sections = area.sections(); +/// let mut builder = CompactVectorBuilder::with_capacity(3, 3, &mut sections)?; +/// builder.set_ints(0..2, [7, 2])?; /// builder.set_int(0, 5)?; /// let cv = builder.freeze(); /// @@ -168,60 +173,54 @@ impl CompactVectorBuilder { /// # Ok(()) /// # } /// ``` -#[derive(Clone, PartialEq, Eq)] +#[derive(Clone)] pub struct CompactVector { chunks: BitVector, len: usize, width: usize, + handle: SectionHandle, } +impl PartialEq for CompactVector { + fn eq(&self, other: &Self) -> bool { + self.chunks == other.chunks + && self.len == other.len + && self.width == other.width + && self.handle.offset == other.handle.offset + && self.handle.len == other.handle.len + } +} + +impl Eq for CompactVector {} + impl Default for CompactVector { fn default() -> Self { + let mut area = ByteArea::new().expect("byte area"); + let mut sections = area.sections(); + let builder = BitVectorBuilder::with_capacity(0, &mut sections).unwrap(); + let handle = builder.handle(); + let chunks = builder.freeze::(); Self { - chunks: BitVectorBuilder::new().freeze::(), + chunks, len: 0, width: 0, + handle, } } } -/// Metadata returned by [`CompactVector::to_bytes`] and required by -/// [`CompactVector::from_bytes`]. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// Metadata describing a [`CompactVector`] stored in a [`ByteArea`]. +#[derive(Debug, Clone, Copy)] pub struct CompactVectorMeta { /// Number of integers stored. pub len: usize, /// Bit width for each integer. pub width: usize, + /// Handle to the raw `u64` words backing the vector. + pub handle: SectionHandle, } impl CompactVector { - /// Creates a new empty builder storing integers within `width` bits each. - /// - /// # Arguments - /// - /// - `width`: Number of bits used to store an integer. - /// - /// # Errors - /// - /// An error is returned if `width` is not in `1..=64`. - /// - /// # Examples - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// use jerky::int_vectors::CompactVector; - /// - /// let cv = CompactVector::new(3)?.freeze(); - /// assert_eq!(cv.len(), 0); - /// assert_eq!(cv.width(), 3); - /// # Ok(()) - /// # } - /// ``` - pub fn new(width: usize) -> Result { - CompactVectorBuilder::new(width) - } - /// Creates a new builder storing integers in `width` bits and /// reserving space for at least `capa` integers. /// @@ -240,7 +239,10 @@ impl CompactVector { /// # fn main() -> Result<(), Box> { /// use jerky::int_vectors::CompactVector; /// - /// let cv = CompactVector::with_capacity(10, 3)?.freeze(); + /// use anybytes::ByteArea; + /// let mut area = ByteArea::new()?; + /// let mut sections = area.sections(); + /// let cv = CompactVector::with_capacity(10, 3, &mut sections)?.freeze(); /// /// assert_eq!(cv.len(), 0); /// assert_eq!(cv.width(), 3); @@ -248,8 +250,12 @@ impl CompactVector { /// # Ok(()) /// # } /// ``` - pub fn with_capacity(capa: usize, width: usize) -> Result { - CompactVectorBuilder::with_capacity(capa, width) + pub fn with_capacity<'a>( + capa: usize, + width: usize, + writer: &mut SectionWriter<'a>, + ) -> Result> { + CompactVectorBuilder::with_capacity(capa, width, writer) } /// Creates a new vector storing an integer in `width` bits, @@ -290,9 +296,11 @@ impl CompactVector { "val must fit in width={width} bits, but got {val}." )); } - let mut builder = CompactVectorBuilder::with_capacity(len, width)?; - for _ in 0..len { - builder.push_int(val)?; + let mut area = ByteArea::new().expect("byte area"); + let mut sections = area.sections(); + let mut builder = CompactVectorBuilder::with_capacity(len, width, &mut sections)?; + for i in 0..len { + builder.set_int(i, val)?; } Ok(builder.freeze()) } @@ -336,10 +344,15 @@ impl CompactVector { anyhow!("vals must consist only of values castable into usize.") })?); } - let mut builder = - CompactVectorBuilder::with_capacity(vals.len(), utils::needed_bits(max_int))?; - for x in vals { - builder.push_int(x.to_usize().unwrap())?; + let mut area = ByteArea::new().expect("byte area"); + let mut sections = area.sections(); + let mut builder = CompactVectorBuilder::with_capacity( + vals.len(), + utils::needed_bits(max_int), + &mut sections, + )?; + for (i, x) in vals.iter().enumerate() { + builder.set_int(i, x.to_usize().unwrap())?; } Ok(builder.freeze()) } @@ -424,26 +437,41 @@ impl CompactVector { } /// Serializes the vector into a [`Bytes`] buffer and accompanying metadata. - pub fn to_bytes(&self) -> (CompactVectorMeta, Bytes) { - let (_, bytes) = self.chunks.data.to_bytes(); - ( - CompactVectorMeta { - len: self.len, - width: self.width, - }, - bytes, - ) + /// Returns metadata describing this vector. + pub fn metadata(&self) -> CompactVectorMeta { + ::metadata(self) } /// Reconstructs the vector from zero-copy [`Bytes`] and its metadata. pub fn from_bytes(meta: CompactVectorMeta, bytes: Bytes) -> Result { + ::from_bytes(meta, bytes) + } +} + +impl Serializable for CompactVector { + type Meta = CompactVectorMeta; + + fn metadata(&self) -> Self::Meta { + CompactVectorMeta { + len: self.len, + width: self.width, + handle: self.handle, + } + } + + fn from_bytes(meta: Self::Meta, bytes: Bytes) -> Result { let data_len = meta.len * meta.width; - let data = BitVectorData::from_bytes(data_len, bytes)?; + let words_view = meta.handle.view(&bytes).map_err(|e| anyhow!(e))?; + let data = BitVectorData { + words: words_view, + len: data_len, + }; let chunks = BitVector::new(data, NoIndex); Ok(Self { chunks, len: meta.len, width: meta.width, + handle: meta.handle, }) } } @@ -531,6 +559,8 @@ impl Iterator for Iter<'_> { } } +impl ExactSizeIterator for Iter<'_> {} + impl std::fmt::Debug for CompactVector { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut ints = vec![0; self.len()]; @@ -549,27 +579,11 @@ impl std::fmt::Debug for CompactVector { mod tests { use super::*; - #[test] - fn test_new_oob_0() { - let e = CompactVector::new(0); - assert_eq!( - e.err().map(|x| x.to_string()), - Some("width must be in 1..=64, but got 0.".to_string()) - ); - } - - #[test] - fn test_new_oob_65() { - let e = CompactVector::new(65); - assert_eq!( - e.err().map(|x| x.to_string()), - Some("width must be in 1..=64, but got 65.".to_string()) - ); - } - #[test] fn test_with_capacity_oob_0() { - let e = CompactVector::with_capacity(0, 0); + let mut area = ByteArea::new().unwrap(); + let mut sections = area.sections(); + let e = CompactVector::with_capacity(0, 0, &mut sections); assert_eq!( e.err().map(|x| x.to_string()), Some("width must be in 1..=64, but got 0.".to_string()) @@ -578,7 +592,9 @@ mod tests { #[test] fn test_with_capacity_oob_65() { - let e = CompactVector::with_capacity(0, 65); + let mut area = ByteArea::new().unwrap(); + let mut sections = area.sections(); + let e = CompactVector::with_capacity(0, 65, &mut sections); assert_eq!( e.err().map(|x| x.to_string()), Some("width must be in 1..=64, but got 65.".to_string()) @@ -623,19 +639,21 @@ mod tests { #[test] fn test_set_int_oob() { - let mut builder = CompactVectorBuilder::with_capacity(1, 2).unwrap(); - builder.push_int(0).unwrap(); + let mut area = ByteArea::new().unwrap(); + let mut sections = area.sections(); + let mut builder = CompactVectorBuilder::with_capacity(1, 2, &mut sections).unwrap(); let e = builder.set_int(1, 1); assert_eq!( e.err().map(|x| x.to_string()), - Some("pos must be no greater than self.len()=1, but got 1.".to_string()) + Some("pos must be no greater than self.capacity()=1, but got 1.".to_string()) ); } #[test] fn test_set_int_unfit() { - let mut builder = CompactVectorBuilder::with_capacity(1, 2).unwrap(); - builder.push_int(0).unwrap(); + let mut area = ByteArea::new().unwrap(); + let mut sections = area.sections(); + let mut builder = CompactVectorBuilder::with_capacity(1, 2, &mut sections).unwrap(); let e = builder.set_int(0, 4); assert_eq!( e.err().map(|x| x.to_string()), @@ -644,30 +662,23 @@ mod tests { } #[test] - fn test_push_int_unfit() { - let mut builder = CompactVectorBuilder::new(2).unwrap(); - let e = builder.push_int(4); + fn test_set_ints_mismatch() { + let mut area = ByteArea::new().unwrap(); + let mut sections = area.sections(); + let mut builder = CompactVectorBuilder::with_capacity(2, 2, &mut sections).unwrap(); + let e = builder.set_ints(0..2, [1]); assert_eq!( e.err().map(|x| x.to_string()), - Some("val must fit in self.width()=2 bits, but got 4.".to_string()) - ); - } - - #[test] - fn test_extend_unfit() { - let mut builder = CompactVectorBuilder::new(2).unwrap(); - let e = builder.extend([4]); - assert_eq!( - e.err().map(|x| x.to_string()), - Some("val must fit in self.width()=2 bits, but got 4.".to_string()) + Some("not enough values for the specified range".to_string()) ); } #[test] fn test_64b() { - let mut builder = CompactVectorBuilder::new(64).unwrap(); - builder.push_int(42).unwrap(); - assert_eq!(builder.clone().freeze().get_int(0), Some(42)); + let mut area = ByteArea::new().unwrap(); + let mut sections = area.sections(); + let mut builder = CompactVectorBuilder::with_capacity(1, 64, &mut sections).unwrap(); + builder.set_int(0, 42).unwrap(); builder.set_int(0, 334).unwrap(); let cv = builder.freeze(); assert_eq!(cv.get_int(0), Some(334)); @@ -695,7 +706,8 @@ mod tests { #[test] fn from_bytes_roundtrip() { let cv = CompactVector::from_slice(&[4, 5, 6]).unwrap(); - let (meta, bytes) = cv.to_bytes(); + let meta = cv.metadata(); + let bytes = cv.chunks.data.words.clone().bytes(); let other = CompactVector::from_bytes(meta, bytes).unwrap(); assert_eq!(cv, other); } diff --git a/src/int_vectors/dacs_byte.rs b/src/int_vectors/dacs_byte.rs index 4e91393..1a4a283 100644 --- a/src/int_vectors/dacs_byte.rs +++ b/src/int_vectors/dacs_byte.rs @@ -8,8 +8,10 @@ use num_traits::ToPrimitive; use crate::bit_vector::{self, BitVector, BitVectorBuilder, BitVectorIndex, Rank, Rank9SelIndex}; use crate::int_vectors::{Access, Build, NumVals}; +use crate::serialization::Serializable; use crate::utils; -use anybytes::{Bytes, View}; +use anybytes::{area::SectionHandle, ByteArea, Bytes, SectionWriter, View}; +use zerocopy::{FromBytes, Immutable}; const LEVEL_WIDTH: usize = 8; const LEVEL_MASK: usize = (1 << LEVEL_WIDTH) - 1; @@ -36,18 +38,20 @@ const MAX_LEVELS: usize = (usize::BITS as usize + LEVEL_WIDTH - 1) / LEVEL_WIDTH /// /// ``` /// # fn main() -> Result<(), Box> { -/// use jerky::int_vectors::{DacsByte, Access}; -/// +/// use anybytes::ByteArea; +/// use jerky::int_vectors::{Access, DacsByte}; /// use jerky::bit_vector::rank9sel::Rank9SelIndex; -/// let seq = DacsByte::::from_slice(&[5, 0, 100000, 334])?; -/// -/// assert_eq!(seq.access(0), Some(5)); -/// assert_eq!(seq.access(1), Some(0)); -/// assert_eq!(seq.access(2), Some(100000)); -/// assert_eq!(seq.access(3), Some(334)); +/// let mut area = ByteArea::new()?; +/// let mut writer = area.sections(); +/// let seq = DacsByte::::from_slice(&[5, 0, 100000, 334], &mut writer)?; +/// let meta = seq.metadata(); +/// let bytes = area.freeze()?; // finalize arena +/// let other = DacsByte::::from_bytes(meta, bytes.clone())?; +/// assert_eq!(seq, other); +/// assert_eq!(other.access(2), Some(100000)); /// -/// assert_eq!(seq.len(), 4); -/// assert_eq!(seq.num_levels(), 3); +/// assert_eq!(other.len(), 4); +/// assert_eq!(other.num_levels(), 3); /// # Ok(()) /// # } /// ``` @@ -56,49 +60,42 @@ const MAX_LEVELS: usize = (usize::BITS as usize + LEVEL_WIDTH - 1) / LEVEL_WIDTH /// /// - N. R. Brisaboa, S. Ladra, and G. Navarro, "DACs: Bringing direct access to variable-length /// codes." Information Processing & Management, 49(1), 392-404, 2013. -#[derive(Clone, PartialEq, Eq)] +#[derive(Clone)] pub struct DacsByte { data: Vec>, flags: Vec>, + handles: SectionHandle, } -/// Metadata required to reconstruct a `DacsByte` from bytes. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct DacsByteMeta { - /// Number of valid levels stored. - pub num_levels: usize, - /// Byte length for each level in order. - pub level_lens: Vec, - /// Metadata for each flag bit vector between levels. - pub flag_meta: Vec, -} - -/// Metadata describing a flag bit vector stored inside a `DacsByte`. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct FlagMeta { - /// Number of bits stored in the bit vector. - pub len_bits: usize, - /// Number of machine words used to hold the bits. - pub num_words: usize, +impl PartialEq for DacsByte { + fn eq(&self, other: &Self) -> bool { + self.data == other.data + && self.flags == other.flags + && self.handles.offset == other.handles.offset + && self.handles.len == other.handles.len + } } -impl Default for FlagMeta { - fn default() -> Self { - Self { - len_bits: 0, - num_words: 0, - } - } +impl Eq for DacsByte {} + +/// Per-level metadata storing handles for payload bytes and flag bit vectors. +#[derive(Debug, Clone, Copy, FromBytes, Immutable)] +pub struct LevelMeta { + /// Handle to the level's payload bytes. + pub level: SectionHandle, + /// Handle to the flag bit vector words (empty for last level). + pub flag: SectionHandle, + /// Number of bits stored in the flag bit vector. + pub flag_bits: usize, } -impl Default for DacsByteMeta { - fn default() -> Self { - Self { - num_levels: 0, - level_lens: Vec::new(), - flag_meta: Vec::new(), - } - } +/// Metadata required to reconstruct a `DacsByte` from bytes. +#[derive(Debug, Clone, Copy)] +pub struct DacsByteMeta { + /// Number of valid levels stored. + pub num_levels: usize, + /// Handle to a slice of per-level [`LevelMeta`] structures. + pub levels: SectionHandle, } impl DacsByte { @@ -107,18 +104,38 @@ impl DacsByte { /// # Arguments /// /// - `vals`: Slice of integers to be stored. + /// - `writer`: Memory allocator used for all internal buffers. /// /// # Errors /// /// An error is returned if `vals` contains an integer that cannot be cast to [`usize`]. - pub fn from_slice(vals: &[T]) -> Result + pub fn from_slice<'a, T>(vals: &[T], writer: &mut SectionWriter<'a>) -> Result where T: ToPrimitive, { if vals.is_empty() { - return Ok(Self::default()); + let data_sec = writer.reserve::(0)?; + let data_handle = data_sec.handle(); + let flag_sec = writer.reserve::(0)?; + let flag_handle = flag_sec.handle(); + let mut infos = writer.reserve::(1)?; + infos[0] = LevelMeta { + level: data_handle, + flag: flag_handle, + flag_bits: 0, + }; + let handles = infos.handle(); + infos.freeze()?; + flag_sec.freeze()?; + data_sec.freeze()?; + return Ok(Self { + data: vec![Bytes::empty().view::<[u8]>().unwrap()], + flags: vec![], + handles, + }); } + // Determine the number of levels by scanning for the maximum value. let mut maxv = 0; for x in vals { maxv = @@ -130,42 +147,119 @@ impl DacsByte { let num_levels = utils::ceiled_divide(num_bits, LEVEL_WIDTH); assert_ne!(num_levels, 0); + // Single-level case: just reserve the bytes and return. if num_levels == 1 { - let data: Vec<_> = vals - .iter() - .map(|x| u8::try_from(x.to_usize().unwrap()).unwrap()) - .collect(); + let mut section = writer.reserve::(vals.len())?; + let slice = section.as_mut_slice(); + for (i, x) in vals.iter().enumerate() { + slice[i] = u8::try_from(x.to_usize().unwrap()).unwrap(); + } + let level_handle = section.handle(); + let flag_sec = writer.reserve::(0)?; + let flag_handle = flag_sec.handle(); + let mut infos = writer.reserve::(1)?; + infos[0] = LevelMeta { + level: level_handle, + flag: flag_handle, + flag_bits: 0, + }; + let handles = infos.handle(); + infos.freeze()?; + flag_sec.freeze()?; + let data = section.freeze()?.view::<[u8]>()?; return Ok(Self { - data: vec![Bytes::from_source(data).view::<[u8]>().unwrap()], + data: vec![data], flags: vec![], + handles, }); } - let mut data = vec![vec![]; num_levels]; - let mut flags = vec![BitVectorBuilder::new(); num_levels - 1]; + // First pass: count flags per level and determine payload lengths. + let mut flag_counts = vec![0usize; num_levels - 1]; + let mut level_lens = vec![0usize; num_levels]; + for val in vals { + let mut x = val + .to_usize() + .ok_or_else(|| anyhow!("vals must consist only of values castable into usize."))?; + let mut j = 0; + loop { + level_lens[j] += 1; + if j == num_levels - 1 { + break; + } + x >>= LEVEL_WIDTH; + flag_counts[j] += 1; + if x == 0 { + break; + } + j += 1; + } + } + // Allocate metadata table, level sections, flag builders, and a dummy flag handle. + let mut infos = writer.reserve::(num_levels)?; + let mut flags: Vec = flag_counts + .iter() + .map(|&c| BitVectorBuilder::with_capacity(c, writer)) + .collect::>()?; + let dummy_flag_sec = writer.reserve::(0)?; + let dummy_flag_handle = dummy_flag_sec.handle(); + let mut levels: Vec<_> = level_lens + .iter() + .map(|&len| writer.reserve::(len)) + .collect::>()?; + for j in 0..num_levels { + let mut meta = LevelMeta { + level: levels[j].handle(), + flag: dummy_flag_handle, + flag_bits: 0, + }; + if j + 1 < num_levels { + meta.flag = flags[j].handle(); + meta.flag_bits = flag_counts[j]; + } + infos[j] = meta; + } + dummy_flag_sec.freeze()?; + + // Offsets for writing into levels and flags. + let mut level_offs = vec![0usize; num_levels]; + let mut flag_offs = vec![0usize; num_levels - 1]; - for x in vals { - let mut x = x.to_usize().unwrap(); + // Second pass: populate levels and flags in place. + for val in vals { + let mut x = val.to_usize().unwrap(); for j in 0..num_levels { - data[j].push(u8::try_from(x & LEVEL_MASK).unwrap()); + levels[j].as_mut_slice()[level_offs[j]] = u8::try_from(x & LEVEL_MASK).unwrap(); + level_offs[j] += 1; x >>= LEVEL_WIDTH; if j == num_levels - 1 { assert_eq!(x, 0); break; } else if x == 0 { - flags[j].push_bit(false); + flags[j].set_bit(flag_offs[j], false)?; + flag_offs[j] += 1; break; + } else { + flags[j].set_bit(flag_offs[j], true)?; + flag_offs[j] += 1; } - flags[j].push_bit(true); } } - let flags = flags.into_iter().map(|bvb| bvb.freeze::()).collect(); - let data = data + // Freeze level sections, flag builders, and metadata table. + let data = levels .into_iter() - .map(|v| Bytes::from_source(v).view::<[u8]>().unwrap()) - .collect(); - Ok(Self { data, flags }) + .map(|sec| sec.freeze()?.view::<[u8]>().map_err(|e| anyhow!(e))) + .collect::>>()?; + let flags = flags.into_iter().map(|bvb| bvb.freeze::()).collect(); + let handles = infos.handle(); + infos.freeze()?; + + Ok(Self { + data, + flags, + handles, + }) } /// Creates an iterator for enumerating integers. @@ -174,10 +268,14 @@ impl DacsByte { /// /// ``` /// # fn main() -> Result<(), Box> { + /// use anybytes::ByteArea; /// use jerky::bit_vector::rank9sel::Rank9SelIndex; /// use jerky::int_vectors::DacsByte; /// - /// let seq = DacsByte::::from_slice(&[5, 0, 100000, 334])?; + /// let mut area = ByteArea::new()?; + /// let mut writer = area.sections(); + /// let seq = DacsByte::::from_slice(&[5, 0, 100000, 334], &mut writer)?; + /// area.freeze()?; /// let mut it = seq.iter(); /// /// assert_eq!(it.next(), Some(5)); @@ -221,102 +319,86 @@ impl DacsByte { self.data.iter().map(|_| LEVEL_WIDTH).collect() } - /// Serializes the sequence into a [`Bytes`] buffer. - /// - /// Returns the metadata necessary for [`from_bytes`]. - pub fn to_bytes(&self) -> (DacsByteMeta, Bytes) { - let level_lens = self.data.iter().map(|v| v.len()).collect::>(); - let flag_meta = self - .flags - .iter() - .map(|f| FlagMeta { - len_bits: f.data.len, - num_words: f.data.num_words(), - }) - .collect::>(); - - let mut buf: Vec = Vec::new(); - for flag in &self.flags { - for &word in flag.data.words.as_ref() { - buf.extend_from_slice(&word.to_ne_bytes()); - } - } - - for level in &self.data { - buf.extend_from_slice(level.as_ref()); - } - - ( - DacsByteMeta { - num_levels: self.data.len(), - level_lens, - flag_meta, - }, - Bytes::from_source(buf), - ) + /// Returns metadata describing this sequence. + pub fn metadata(&self) -> DacsByteMeta { + ::metadata(self) } /// Reconstructs the sequence from zero-copy [`Bytes`]. - /// - /// The `meta` argument should come from [`to_bytes`]. pub fn from_bytes(meta: DacsByteMeta, bytes: Bytes) -> Result { - use std::mem::size_of; + ::from_bytes(meta, bytes) + } +} + +impl Serializable for DacsByte { + type Meta = DacsByteMeta; - let usize_size = size_of::(); - let mut cursor = 0; - let slice = bytes.as_ref(); + fn metadata(&self) -> Self::Meta { + DacsByteMeta { + num_levels: self.data.len(), + levels: self.handles, + } + } - if meta.num_levels == 0 - || meta.num_levels > MAX_LEVELS - || meta.level_lens.len() != meta.num_levels - || meta.flag_meta.len() != meta.num_levels.saturating_sub(1) - { + fn from_bytes(meta: Self::Meta, bytes: Bytes) -> Result { + if meta.num_levels == 0 || meta.num_levels > MAX_LEVELS { return Err(anyhow!("invalid metadata")); } - let mut flags = Vec::with_capacity(meta.flag_meta.len()); - for fm in &meta.flag_meta { - let bytes_len = fm.num_words * usize_size; - if cursor + bytes_len > slice.len() { - return Err(anyhow!("insufficient bytes")); - } - let words_view = bytes - .slice_to_bytes(&slice[cursor..cursor + bytes_len]) - .ok_or_else(|| anyhow!("invalid slice"))? - .view::<[usize]>() - .map_err(|e| anyhow!(e))?; - cursor += bytes_len; - let data = bit_vector::BitVectorData { - words: words_view, - len: fm.len_bits, - }; - let index = I::build(&data); - flags.push(bit_vector::BitVector { data, index }); + let infos = meta.levels.view(&bytes).map_err(anyhow::Error::from)?; + if infos.as_ref().len() != meta.num_levels { + return Err(anyhow!("invalid metadata")); } + let mut flags = Vec::with_capacity(meta.num_levels.saturating_sub(1)); let mut data = Vec::with_capacity(meta.num_levels); - for &len in &meta.level_lens { - if cursor + len > slice.len() { - return Err(anyhow!("insufficient bytes")); + for (idx, info) in infos.as_ref().iter().enumerate() { + if idx + 1 < meta.num_levels { + let words = info.flag.view(&bytes).map_err(anyhow::Error::from)?; + let bv_data = bit_vector::BitVectorData { + words, + len: info.flag_bits, + }; + let index = I::build(&bv_data); + flags.push(bit_vector::BitVector { + data: bv_data, + index, + }); } - let view_bytes = bytes - .slice_to_bytes(&slice[cursor..cursor + len]) - .ok_or_else(|| anyhow!("invalid slice"))?; - let view = view_bytes.view::<[u8]>().map_err(|e| anyhow!(e))?; - data.push(view); - cursor += len; + let lvl_view = info.level.view(&bytes).map_err(anyhow::Error::from)?; + data.push(lvl_view); } - Ok(Self { data, flags }) + Ok(Self { + data, + flags, + handles: meta.levels, + }) } } impl Default for DacsByte { fn default() -> Self { + let mut area = ByteArea::new().expect("byte area"); + let mut writer = area.sections(); + let data_sec = writer.reserve::(0).expect("reserve section"); + let data_handle = data_sec.handle(); + let flag_sec = writer.reserve::(0).expect("reserve flag"); + let flag_handle = flag_sec.handle(); + let mut infos = writer.reserve::(1).expect("reserve level meta"); + infos[0] = LevelMeta { + level: data_handle, + flag: flag_handle, + flag_bits: 0, + }; + let handles = infos.handle(); + infos.freeze().expect("freeze level meta"); + flag_sec.freeze().expect("freeze flag"); + data_sec.freeze().expect("freeze section"); Self { - // Needs a single level at least. data: vec![Bytes::empty().view::<[u8]>().unwrap()], flags: vec![], + handles, } } } @@ -330,7 +412,12 @@ impl Build for DacsByte { T: ToPrimitive, Self: Sized, { - Self::from_slice(vals) + let mut area = ByteArea::new()?; + let mut writer = area.sections(); + let seq = Self::from_slice(vals, &mut writer)?; + // Ensure the area is sealed so the bytes become immutable. + area.freeze()?; + Ok(seq) } } @@ -353,10 +440,14 @@ impl Access for DacsByte { /// /// ``` /// # fn main() -> Result<(), Box> { + /// use anybytes::ByteArea; /// use jerky::bit_vector::rank9sel::Rank9SelIndex; /// use jerky::int_vectors::{DacsByte, Access}; /// - /// let seq = DacsByte::::from_slice(&[5, 999, 334])?; + /// let mut area = ByteArea::new()?; + /// let mut writer = area.sections(); + /// let seq = DacsByte::::from_slice(&[5, 999, 334], &mut writer)?; + /// area.freeze()?; /// /// assert_eq!(seq.access(0), Some(5)); /// assert_eq!(seq.access(1), Some(999)); @@ -429,12 +520,16 @@ impl Iterator for Iter<'_, I> { #[cfg(test)] mod tests { use super::*; - use anybytes::Bytes; + use anybytes::{ByteArea, Bytes}; #[test] fn test_basic() { + let mut area = ByteArea::new().unwrap(); + let mut writer = area.sections(); let seq = - DacsByte::::from_slice(&[0xFFFF, 0xFF, 0xF, 0xFFFFF, 0xF]).unwrap(); + DacsByte::::from_slice(&[0xFFFF, 0xFF, 0xF, 0xFFFFF, 0xF], &mut writer) + .unwrap(); + area.freeze().unwrap(); let expected = vec![ Bytes::from_source(vec![0xFFu8, 0xFF, 0xF, 0xFF, 0xF]) @@ -447,11 +542,14 @@ mod tests { ]; assert_eq!(seq.data, expected); - let mut b = BitVectorBuilder::new(); - b.extend_bits([true, false, false, true, false]); + let mut area = ByteArea::new().unwrap(); + let mut sections = area.sections(); + let mut b = BitVectorBuilder::with_capacity(5, &mut sections).unwrap(); + b.set_bit(0, true).unwrap(); + b.set_bit(3, true).unwrap(); let f0 = b.freeze::>(); - let mut b = BitVectorBuilder::new(); - b.extend_bits([false, true]); + let mut b = BitVectorBuilder::with_capacity(2, &mut sections).unwrap(); + b.set_bit(1, true).unwrap(); let f1 = b.freeze::>(); assert_eq!(seq.flags, vec![f0, f1]); @@ -469,7 +567,10 @@ mod tests { #[test] fn test_empty() { - let seq = DacsByte::::from_slice::(&[]).unwrap(); + let mut area = ByteArea::new().unwrap(); + let mut writer = area.sections(); + let seq = DacsByte::::from_slice::(&[], &mut writer).unwrap(); + area.freeze().unwrap(); assert!(seq.is_empty()); assert_eq!(seq.len(), 0); assert_eq!(seq.num_levels(), 1); @@ -478,7 +579,10 @@ mod tests { #[test] fn test_all_zeros() { - let seq = DacsByte::::from_slice(&[0, 0, 0, 0]).unwrap(); + let mut area = ByteArea::new().unwrap(); + let mut writer = area.sections(); + let seq = DacsByte::::from_slice(&[0, 0, 0, 0], &mut writer).unwrap(); + area.freeze().unwrap(); assert!(!seq.is_empty()); assert_eq!(seq.len(), 4); assert_eq!(seq.num_levels(), 1); @@ -491,28 +595,39 @@ mod tests { #[test] fn iter_collects() { - let seq = DacsByte::::from_slice(&[5, 7]).unwrap(); + let mut area = ByteArea::new().unwrap(); + let mut writer = area.sections(); + let seq = DacsByte::::from_slice(&[5, 7], &mut writer).unwrap(); + area.freeze().unwrap(); let collected: Vec = seq.iter().collect(); assert_eq!(collected, vec![5, 7]); } #[test] fn to_vec_collects() { - let seq = DacsByte::::from_slice(&[5, 7]).unwrap(); + let mut area = ByteArea::new().unwrap(); + let mut writer = area.sections(); + let seq = DacsByte::::from_slice(&[5, 7], &mut writer).unwrap(); + area.freeze().unwrap(); assert_eq!(seq.to_vec(), vec![5, 7]); } #[test] fn bytes_roundtrip() { - let seq = DacsByte::::from_slice(&[5, 0, 100000, 334]).unwrap(); - let (meta, bytes) = seq.to_bytes(); + let mut area = ByteArea::new().unwrap(); + let mut writer = area.sections(); + let seq = DacsByte::::from_slice(&[5, 0, 100000, 334], &mut writer).unwrap(); + let bytes = area.freeze().unwrap(); + let meta = seq.metadata(); let other = DacsByte::::from_bytes(meta, bytes).unwrap(); assert_eq!(seq, other); } #[test] fn test_from_slice_uncastable() { - let e = DacsByte::::from_slice(&[u128::MAX]); + let mut area = ByteArea::new().unwrap(); + let mut writer = area.sections(); + let e = DacsByte::::from_slice(&[u128::MAX], &mut writer); assert_eq!( e.err().map(|x| x.to_string()), Some("vals must consist only of values castable into usize.".to_string()) diff --git a/src/lib.rs b/src/lib.rs index a320444..0ecaad8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,10 +56,12 @@ pub mod char_sequences; pub mod data; pub mod int_vectors; mod intrinsics; +pub mod serialization; pub mod utils; pub use bit_vector::{BitVector, BitVectorData, BitVectorIndex, NoIndex}; pub use data::IntVectorData; +pub use serialization::Serializable; // NOTE(kampersanda): We should not use `get()` because it has been already used in most std // containers with different type annotations. diff --git a/src/serialization.rs b/src/serialization.rs new file mode 100644 index 0000000..66b7619 --- /dev/null +++ b/src/serialization.rs @@ -0,0 +1,21 @@ +//! Zero-copy serialization utilities. + +use anybytes::Bytes; +use anyhow::Result; + +/// Types that can be reconstructed from frozen [`Bytes`] using metadata. +/// +/// Implementors write their data into a [`ByteArea`](anybytes::ByteArea) and +/// expose lightweight metadata that describes where their bytes live. Given +/// the metadata and the full `Bytes` region, the type can be rebuilt without +/// copying. +pub trait Serializable: Sized { + /// Metadata describing the byte layout required to reconstruct `Self`. + type Meta; + + /// Returns metadata for this instance. + fn metadata(&self) -> Self::Meta; + + /// Rebuilds an instance from metadata and the arena's frozen bytes. + fn from_bytes(meta: Self::Meta, bytes: Bytes) -> Result; +} diff --git a/src/utils.rs b/src/utils.rs index 54fc365..208cedb 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -17,7 +17,7 @@ use crate::broadword; /// assert_eq!(needed_bits(256), 9); /// ``` pub fn needed_bits(x: usize) -> usize { - broadword::msb(x).map_or(1, |n| n + 1) + broadword::msb(x as u64).map_or(1, |n| n + 1) } /// Returns `ceil(x / y)`.