Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
7af2ede
Optimize packed code extraction
jcfangc Sep 14, 2026
bbf04d9
Optimize packed view access
jcfangc Sep 16, 2026
151e8a5
Share packed code extraction
jcfangc Sep 16, 2026
215e980
Optimize packed construction
jcfangc Sep 16, 2026
2ec5fcf
Adopt owned bit-string words
jcfangc Sep 19, 2026
09a7759
Add packed word block backend
jcfangc Sep 19, 2026
b154885
Specialize scalar packing for eight-bit codes
jcfangc Sep 19, 2026
7c45c3c
Specialize scalar packing for four-bit codes
jcfangc Sep 19, 2026
b5d4324
Specialize scalar packing for two-bit codes
jcfangc Sep 19, 2026
42d82eb
Optimize irregular packed widths in blocks
jcfangc Sep 19, 2026
50884f6
Specialize scalar packing for one-bit codes
jcfangc Sep 19, 2026
be42264
Batch packed construction before SIMD
jcfangc Sep 19, 2026
e9cbfb8
Prepare packed word packing backends
jcfangc Sep 19, 2026
eb3b172
Finalize packed word packing contract
jcfangc Sep 19, 2026
9476d3a
Add AVX2 nibble packing backend
jcfangc Sep 20, 2026
3d6db1f
Add AVX2 bit-mask packing backend
jcfangc Sep 20, 2026
028c8c0
Document AVX2 packing support matrix
jcfangc Sep 20, 2026
da342d3
Add AVX2 six-bit packing backend
jcfangc Sep 20, 2026
58b63d7
Add AVX2 three-bit packing backend
jcfangc Sep 20, 2026
6e08105
Add AVX2 five-bit packing backend
jcfangc Sep 20, 2026
a10ad6f
Add AVX2 seven-bit packing backend
jcfangc Sep 20, 2026
cc36fbf
Freeze AVX2 packing thresholds
jcfangc Sep 20, 2026
1d65434
Organize AVX2 packing kernels
jcfangc Sep 20, 2026
854cb1a
Optimize sequential packed iteration
jcfangc Sep 26, 2026
e8f7d60
Add packed benchmarks for widths five and six
jcfangc Sep 29, 2026
5aaa220
Optimize packed iteration with a rolling window
jcfangc Sep 29, 2026
ae9d107
Add AVX2 packed bulk unpack for conversion
jcfangc Sep 30, 2026
8ab8113
Avoid slow random access for short packed conversion
jcfangc Sep 30, 2026
bfd3f12
Unpack complete layout blocks in conversion tail
jcfangc Oct 1, 2026
3803ddb
Clarify conversion layout length name
jcfangc Oct 1, 2026
d4f689d
Expand AVX2 unpack width support
jcfangc Oct 1, 2026
6d3e690
Strengthen AVX2 unpack regression coverage
jcfangc Oct 1, 2026
5ce7012
Use bulk unpack only with accelerated backend
jcfangc Oct 1, 2026
8ab293f
Clarify bulk unpack capability naming
jcfangc Oct 7, 2026
b31ec03
Use unchecked word refill in packed iterator
jcfangc Oct 7, 2026
b9f3b5f
Use unchecked word loads for packed get
jcfangc Oct 7, 2026
eeef026
Document packed view alignment invariants
jcfangc Oct 7, 2026
621718c
Optimize packed view iteration with rolling cursor
jcfangc Oct 7, 2026
d9296dd
Cover aligned packed view iterator starts
jcfangc Oct 7, 2026
70c5de7
Use packed bit append for character push
jcfangc Oct 7, 2026
51df6fa
Specialize byte-width packed push
jcfangc Oct 11, 2026
c6cdbf0
Optimize packed insertion and removal with fixed-width shifts
jcfangc Oct 14, 2026
390d58f
Optimize in-place word shifts with a carry stream
jcfangc Oct 15, 2026
3cf8fa6
Optimize AVX2 in-place right shifts with word lookahead
jcfangc Oct 16, 2026
30591e9
Document SSE2 carry prototype codegen and negative results
jcfangc Oct 17, 2026
09ac225
Experiment with SSE2 vector lookahead for in-place right shifts
jcfangc Oct 17, 2026
2dafe6a
Reuse packed character push in Extend
jcfangc Oct 17, 2026
431179e
Batch packed codes when extending PackedString
jcfangc Oct 17, 2026
6e3c688
Document and benchmark batched packed extension
jcfangc Oct 17, 2026
dc8e55a
Bump workspace version to 0.6.1
jcfangc Oct 20, 2026
a180923
Distinguish packing and unpacking batch constants
jcfangc Oct 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 13 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ exclude = ["/benches", "/src/**/tests_for_*", "/tests", ".github/"]
members = ["derive"]

[workspace.package]
version = "0.6.0"
version = "0.6.1"

[features]
characterization = []
Expand Down Expand Up @@ -128,6 +128,10 @@ harness = false
name = "packed_construction"
harness = false

[[bench]]
name = "packed_thresholds"
harness = false

[[bench]]
name = "packed_access"
harness = false
Expand All @@ -136,6 +140,10 @@ harness = false
name = "packed_iteration"
harness = false

[[bench]]
name = "packed_conversion"
harness = false

[[bench]]
name = "packed_editing"
harness = false
Expand All @@ -152,6 +160,10 @@ harness = false
name = "bit_ops_shl_assign"
harness = false

[[bench]]
name = "bit_ops_shr_assign"
harness = false

[[bench]]
name = "hash"
harness = false
Expand Down
63 changes: 63 additions & 0 deletions benches/bit_ops_shr_assign.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use bit_string::BitString;
use divan::{Bencher, black_box};

fn main() {
divan::main();
}

// Preparation is outside timing. Repeated shifts reuse one allocation, so
// they measure steady-state throughput separately from a single operation.
fn assign(bencher: Bencher, len: usize, amount: usize, count: usize) {
let input = make_bit_string(len);
bencher.with_inputs(|| input.clone()).bench_refs(|bits| {
for _ in 0..count {
bits.shr_assign(black_box(amount));
}
black_box(&*bits);
});
}

fn owned(bencher: Bencher, len: usize, amount: usize) {
let input = make_bit_string(len);
// Output allocation is part of the owned operation.
bencher.bench(|| black_box(black_box(&input).shr(black_box(amount))));
}

fn make_bit_string(len: usize) -> BitString {
let mut state = 0x9e37_79b9_7f4a_7c15u64;
(0..len)
.map(|_| {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state & 1 != 0
})
.collect()
}

macro_rules! define_shift {
($case:ident, $amount:literal) => {
mod $case {
use super::*;

#[divan::bench(args = [4096, 65_536, 1_048_576])]
fn assign(bencher: Bencher, len: usize) {
super::assign(bencher, len, $amount, 1);
}

#[divan::bench(args = [4096, 65_536, 1_048_576])]
fn repeated_256(bencher: Bencher, len: usize) {
super::assign(bencher, len, $amount, 256);
}

#[divan::bench(args = [4096, 65_536, 1_048_576])]
fn owned(bencher: Bencher, len: usize) {
super::owned(bencher, len, $amount);
}
}
};
}

define_shift!(by_1, 1);
define_shift!(by_17, 17);
define_shift!(by_65, 65);
93 changes: 91 additions & 2 deletions benches/packed_competitors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use compressed_intvec::fixed::UFixedVec;
use divan::{Bencher, black_box};
use grit_bitvec::{TypedBitElem, TypedBitVec};
use support::{
aligned_index, aligned_index_for, codes, cross_word_index, cross_word_index_for, indices,
unaligned_index, unaligned_index_for,
Code, aligned_index, aligned_index_for, codes, cross_word_index, cross_word_index_for, indices,
packed, unaligned_index, unaligned_index_for,
};
use sux::bits::BitFieldVec;
use value_traits::slices::{SliceByValue, SliceByValueMut};
Expand Down Expand Up @@ -309,6 +309,89 @@ where
});
}

// Batch timings include every insertion and any capacity growth it triggers.
// Keep the batch no larger than the initial length: truncating a cloned packed
// input then retains enough capacity even with BitString's lazy shrink policy.
const INSERT_BATCH_LEN: usize = 256;

fn insert_reserved_packed<const BITS: u8>(bencher: Bencher, len: usize) {
insert_sequence_packed::<BITS>(bencher, len, 1, true);
}

fn insert_repeated_packed<const BITS: u8>(bencher: Bencher, len: usize) {
insert_sequence_packed::<BITS>(bencher, len, len.min(INSERT_BATCH_LEN), false);
}

fn insert_repeated_reserved_packed<const BITS: u8>(bencher: Bencher, len: usize) {
insert_sequence_packed::<BITS>(bencher, len, len.min(INSERT_BATCH_LEN), true);
}

fn insert_sequence_packed<const BITS: u8>(
bencher: Bencher,
len: usize,
count: usize,
reserve: bool,
) {
let input = packed::<BITS>(&codes(BITS, len + if reserve { count } else { 0 }));
let index = unaligned_index::<BITS>(len);
bencher
.with_inputs(|| {
let mut value = input.clone();
// Clone before truncating: cloning the shorter value would discard
// the spare allocation. Setup is excluded from the timed region.
value.truncate(len);
value
})
.bench_local_refs(|value| {
// A fixed insertion point keeps shifting the original suffix plus
// the characters inserted earlier in this batch.
for _ in 0..count {
value.insert(index, Code(0));
}
black_box(&*value);
black_box(value.char_len())
});
}

fn insert_reserved_grit<T: TypedBitElem<Base = u8> + 'static>(bencher: Bencher, len: usize) {
insert_sequence_grit::<T>(bencher, len, 1, true);
}

fn insert_repeated_grit<T: TypedBitElem<Base = u8> + 'static>(bencher: Bencher, len: usize) {
insert_sequence_grit::<T>(bencher, len, len.min(INSERT_BATCH_LEN), false);
}

fn insert_repeated_reserved_grit<T: TypedBitElem<Base = u8> + 'static>(
bencher: Bencher,
len: usize,
) {
insert_sequence_grit::<T>(bencher, len, len.min(INSERT_BATCH_LEN), true);
}

fn insert_sequence_grit<T: TypedBitElem<Base = u8> + 'static>(
bencher: Bencher,
len: usize,
count: usize,
reserve: bool,
) {
let width = grit_width::<T>();
let input = codes(width, len);
let index = unaligned_index_for(usize::from(width), len);
bencher
.with_inputs(|| {
let mut value = TypedBitVec::<T>::with_capacity(len + if reserve { count } else { 0 });
value.append_iter(input.iter().copied()).unwrap();
value
})
.bench_local_refs(|value| {
for _ in 0..count {
value.insert(index, 0).unwrap();
}
black_box(&*value);
black_box(value.len())
});
}

fn remove_grit<T>(bencher: Bencher, len: usize)
where
T: TypedBitElem<Base = u8> + 'static,
Expand Down Expand Up @@ -376,6 +459,9 @@ macro_rules! define_packed_case {
benchmark!("editing/pop", pop_fixed, "compressed_intvec");
benchmark!("editing/extend_bulk_25pct", extend_fixed, "compressed_intvec");
benchmark!("editing/insert_position/unaligned", insert_fixed, "compressed_intvec");
benchmark!("editing/insert_reserved/unaligned", insert_reserved_packed, "ours_packed_string");
benchmark!("editing/insert_repeated/unaligned", insert_repeated_packed, "ours_packed_string");
benchmark!("editing/insert_repeated_reserved/unaligned", insert_repeated_reserved_packed, "ours_packed_string");
benchmark!("editing/remove_position/word_boundary_or_fallback", remove_fixed, "compressed_intvec");

benchmark!("construction/from_chars", construct_sux, "sux");
Expand Down Expand Up @@ -422,6 +508,9 @@ macro_rules! define_grit_case {
benchmark!("editing/push_boundary_probe", push_grit);
benchmark!("editing/pop", pop_grit);
benchmark!("editing/insert_position/unaligned", insert_grit);
benchmark!("editing/insert_reserved/unaligned", insert_reserved_grit);
benchmark!("editing/insert_repeated/unaligned", insert_repeated_grit);
benchmark!("editing/insert_repeated_reserved/unaligned", insert_repeated_reserved_grit);
benchmark!("editing/remove_position/word_boundary_or_fallback", remove_grit);
benchmark!("editing/extend_bulk_25pct", append_grit);
}
Expand Down
106 changes: 106 additions & 0 deletions benches/packed_conversion.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#[path = "packed_support/mod.rs"]
mod support;

use divan::{Bencher, black_box};
use support::{codes, packed};

fn main() {
divan::main();
}

fn collect_via_iter<const BITS: u8>(bencher: Bencher, len: usize) {
let input = codes(BITS, len);
let value = packed::<BITS>(&input);
bencher.bench(|| black_box(&value).iter().collect::<Vec<_>>());
}

fn convert_to_vec<const BITS: u8>(bencher: Bencher, len: usize) {
let input = codes(BITS, len);
let value = packed::<BITS>(&input);
bencher.bench(|| black_box(&value).to_vec());
}

macro_rules! define_case {
($case:ident, $bits:literal, $len:literal) => {
mod $case {
use super::*;

#[divan::bench(name = concat!("packed_conversion/to_vec/", stringify!($case), "/iter_collect"))]
fn iter_collect(bencher: Bencher) {
super::collect_via_iter::<$bits>(bencher, $len);
}

#[divan::bench(name = concat!("packed_conversion/to_vec/", stringify!($case), "/to_vec"))]
fn to_vec(bencher: Bencher) {
super::convert_to_vec::<$bits>(bencher, $len);
}
}
};
}

crate::for_each_packed_bench_case!(define_case);

macro_rules! define_boundary_case {
($case:ident, $width:ident, $bits:literal, $len:literal) => {
mod $case {
use super::*;

#[divan::bench(name = concat!("packed_conversion/to_vec_boundary/", stringify!($width), "/", stringify!($case), "/iter_collect"))]
fn iter_collect(bencher: Bencher) {
super::super::collect_via_iter::<$bits>(bencher, $len);
}

#[divan::bench(name = concat!("packed_conversion/to_vec_boundary/", stringify!($width), "/", stringify!($case), "/to_vec"))]
fn to_vec(bencher: Bencher) {
super::super::convert_to_vec::<$bits>(bencher, $len);
}
}
};
}

macro_rules! define_width_boundaries {
($width:ident, $bits:literal; $($case:ident: $len:literal),+ $(,)?) => {
mod $width {
use super::*;

$(define_boundary_case!($case, $width, $bits, $len);)+
}
};
}

mod boundaries {
use super::*;

define_width_boundaries!(bits_1, 1;
len_16: 16, len_31: 31, len_32: 32, len_63: 63, len_64: 64,
len_65: 65, len_95: 95, len_96: 96, len_127: 127, len_128: 128, len_129: 129,
);
define_width_boundaries!(bits_2, 2;
len_16: 16, len_31: 31, len_32: 32, len_63: 63, len_64: 64,
len_65: 65, len_95: 95, len_96: 96, len_127: 127, len_128: 128, len_129: 129,
);
define_width_boundaries!(bits_3, 3;
len_16: 16, len_31: 31, len_32: 32, len_63: 63, len_64: 64,
len_65: 65, len_95: 95, len_96: 96, len_127: 127, len_128: 128, len_129: 129,
);
define_width_boundaries!(bits_4, 4;
len_16: 16, len_31: 31, len_32: 32, len_63: 63, len_64: 64,
len_65: 65, len_95: 95, len_96: 96, len_127: 127, len_128: 128, len_129: 129,
);
define_width_boundaries!(bits_5, 5;
len_16: 16, len_31: 31, len_32: 32, len_63: 63, len_64: 64,
len_65: 65, len_95: 95, len_96: 96, len_127: 127, len_128: 128, len_129: 129,
);
define_width_boundaries!(bits_6, 6;
len_16: 16, len_31: 31, len_32: 32, len_63: 63, len_64: 64,
len_65: 65, len_95: 95, len_96: 96, len_127: 127, len_128: 128, len_129: 129,
);
define_width_boundaries!(bits_7, 7;
len_16: 16, len_31: 31, len_32: 32, len_63: 63, len_64: 64,
len_65: 65, len_95: 95, len_96: 96, len_127: 127, len_128: 128, len_129: 129,
);
define_width_boundaries!(bits_8, 8;
len_16: 16, len_31: 31, len_32: 32, len_63: 63, len_64: 64,
len_65: 65, len_95: 95, len_96: 96, len_127: 127, len_128: 128, len_129: 129,
);
}
Loading
Loading