-
Notifications
You must be signed in to change notification settings - Fork 4
feat: native pure-Rust u64 FastPFOR codec #85
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
CommanderStorm
wants to merge
26
commits into
fast-pack:main
Choose a base branch
from
CommanderStorm:feat/native-u64-fastpfor
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 3 commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
36aaa7f
feat: native pure-Rust u64 FastPFOR codec
CommanderStorm 9d76737
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] 53e4c19
docs: de-vibe comments and use one sentence per line
CommanderStorm 10ced00
refactor: one codec type per block size for u32 and u64
CommanderStorm 989b9a3
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] b3d15e2
Update fuzz/fuzz_targets/fastpfor_u64.rs
CommanderStorm 276df9f
refactor: share one width-generic page engine between u32 and u64
CommanderStorm 91d0be5
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] 3b5e457
Merge branch 'main' into feat/native-u64-fastpfor
CommanderStorm 640866d
fix: satisfy nightly clippy and rustfmt for CI
CommanderStorm e17a413
Merge branch 'main' into feat/native-u64-fastpfor
nyurik 620d824
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] c6045e0
wip
nyurik dcf64f7
wip
nyurik 2a2e2da
optimize
nyurik b6f4da0
wip
nyurik 84d4367
wip
nyurik 9b3fbed
wip
nyurik f68089f
refactor more to per-bit files
nyurik 2f9f7b4
Merge branch 'main' into feat/native-u64-fastpfor
nyurik 1df5c8c
fix: allow inline_always on fast_pack/fast_unpack forwarders
CommanderStorm dadcb05
fix: repair broken rustdoc intra-doc links on FastPFor
CommanderStorm 6290d3d
refactor: width-generic codec family, split u32/u64 by type
CommanderStorm b267b64
refactor: source generic integer ops from num_traits::PrimInt
CommanderStorm 2f5fc2c
Apply suggestion from @CommanderStorm
CommanderStorm 056725d
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| #![no_main] | ||
|
|
||
| //! Differential fuzz of the 64-bit FastPFOR codec. | ||
| //! | ||
| //! For any `u64` input, `FastPForWide` and the C++ codec must produce identical compressed bytes. | ||
| //! Every decoder must reproduce the original input. | ||
| //! Each side must also decode the other's output. | ||
|
|
||
| use fastpfor::cpp::{CppFastPFor128, CppFastPFor256}; | ||
| use fastpfor::{BlockCodec64, FastPForWide128, FastPForWide256}; | ||
| use libfuzzer_sys::fuzz_target; | ||
|
|
||
| #[derive(arbitrary::Arbitrary, Debug)] | ||
| struct Input { | ||
| data: Vec<u64>, | ||
| use_256: bool, | ||
| } | ||
|
|
||
| fn check(rust: &mut impl BlockCodec64, cpp: &mut impl BlockCodec64, data: &[u64], name: &str) { | ||
| let mut rust_enc = Vec::new(); | ||
| rust.encode64(data, &mut rust_enc).expect("Rust encode64 failed"); | ||
|
|
||
| let mut cpp_enc = Vec::new(); | ||
| cpp.encode64(data, &mut cpp_enc).expect("C++ encode64 failed"); | ||
|
|
||
| assert_eq!(rust_enc, cpp_enc, "{name}: Rust and C++ encode64 bytes differ"); | ||
|
|
||
| let mut rust_dec = Vec::new(); | ||
| rust.decode64(&rust_enc, &mut rust_dec) | ||
| .expect("Rust decode64 of own output failed"); | ||
| assert_eq!(rust_dec, data, "{name}: Rust roundtrip mismatch"); | ||
|
|
||
| let mut cross = Vec::new(); | ||
| rust.decode64(&cpp_enc, &mut cross) | ||
| .expect("Rust decode64 of C++ output failed"); | ||
| assert_eq!(cross, data, "{name}: Rust could not decode C++ output"); | ||
|
|
||
| let mut cpp_dec = Vec::new(); | ||
| cpp.decode64(&rust_enc, &mut cpp_dec) | ||
| .expect("C++ decode64 of Rust output failed"); | ||
| assert_eq!(cpp_dec, data, "{name}: C++ could not decode Rust output"); | ||
| } | ||
|
|
||
| fuzz_target!(|input: Input| { | ||
| if input.use_256 { | ||
| check( | ||
| &mut FastPForWide256::default(), | ||
| &mut CppFastPFor256::default(), | ||
| &input.data, | ||
| "FastPForWide256", | ||
| ); | ||
| } else { | ||
| check( | ||
| &mut FastPForWide128::default(), | ||
| &mut CppFastPFor128::default(), | ||
| &input.data, | ||
| "FastPForWide128", | ||
| ); | ||
| } | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| //! Generic scalar bit-packing for 64-bit values. | ||
| //! | ||
| //! Packs and unpacks groups of 32 values at any bit width `0..=64`. | ||
| //! The layout is a little-endian bitstream, matching the hand-unrolled 32-bit kernels in [`bitpacking`](super::bitpacking). | ||
| //! Value `j` occupies bits `[j*bit, (j+1)*bit)` of the concatenated stream. | ||
| //! Each call moves exactly `bit` `u32` words. | ||
|
|
||
| const fn low_mask(bit: u8) -> u64 { | ||
| if bit >= 64 { | ||
| u64::MAX | ||
| } else { | ||
| (1u64 << bit) - 1 | ||
| } | ||
| } | ||
|
|
||
| /// Packs 32 values from `input[inpos..]` into `output[outpos..]` at `bit` bits each. | ||
| pub fn pack_wide(input: &[u64], inpos: usize, output: &mut [u32], outpos: usize, bit: u8) { | ||
| if bit == 0 { | ||
| return; | ||
| } | ||
| let mask = u128::from(low_mask(bit)); | ||
| let mut acc: u128 = 0; | ||
| let mut filled: u32 = 0; | ||
| let mut out = outpos; | ||
| for j in 0..32 { | ||
| acc |= (u128::from(input[inpos + j]) & mask) << filled; | ||
| filled += u32::from(bit); | ||
| while filled >= 32 { | ||
| output[out] = acc as u32; | ||
| out += 1; | ||
| acc >>= 32; | ||
| filled -= 32; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// Unpacks 32 values from `input[inpos..]` into `output[outpos..]` at `bit` bits each. | ||
| pub fn unpack_wide(input: &[u32], inpos: usize, output: &mut [u64], outpos: usize, bit: u8) { | ||
| if bit == 0 { | ||
| output[outpos..outpos + 32].fill(0); | ||
| return; | ||
| } | ||
| let mask = u128::from(low_mask(bit)); | ||
| let mut acc: u128 = 0; | ||
| let mut avail: u32 = 0; | ||
| let mut inp = inpos; | ||
| for j in 0..32 { | ||
| while avail < u32::from(bit) { | ||
| acc |= u128::from(input[inp]) << avail; | ||
| inp += 1; | ||
| avail += 32; | ||
| } | ||
| output[outpos + j] = (acc & mask) as u64; | ||
| acc >>= u32::from(bit); | ||
| avail -= u32::from(bit); | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
| use crate::rust::integer_compression::{bitpacking, bitunpacking}; | ||
|
|
||
| #[test] | ||
| fn wide_matches_u32_kernels() { | ||
| let values32: [u32; 32] = std::array::from_fn(|i| (i as u32).wrapping_mul(2_654_435_761)); | ||
|
|
||
| for bit in 1..=32u8 { | ||
| let mask = if bit == 32 { | ||
| u32::MAX | ||
| } else { | ||
| (1u32 << bit) - 1 | ||
| }; | ||
| let masked32: [u32; 32] = std::array::from_fn(|i| values32[i] & mask); | ||
| let masked64: [u64; 32] = std::array::from_fn(|i| u64::from(masked32[i])); | ||
|
|
||
| let mut out_ref = vec![0u32; bit as usize]; | ||
| bitpacking::fast_pack(&masked32, 0, &mut out_ref, 0, bit); | ||
|
|
||
| let mut out_wide = vec![0u32; bit as usize]; | ||
| pack_wide(&masked64, 0, &mut out_wide, 0, bit); | ||
|
|
||
| assert_eq!(out_ref, out_wide, "pack mismatch at bit={bit}"); | ||
|
|
||
| let mut back_ref = vec![0u32; 32]; | ||
| bitunpacking::fast_unpack(&out_ref, 0, &mut back_ref, 0, bit); | ||
| let mut back_wide = vec![0u64; 32]; | ||
| unpack_wide(&out_wide, 0, &mut back_wide, 0, bit); | ||
|
|
||
| for i in 0..32 { | ||
| assert_eq!( | ||
| u64::from(back_ref[i]), | ||
| back_wide[i], | ||
| "unpack mismatch at bit={bit}" | ||
| ); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn wide_roundtrip_all_widths() { | ||
| for bit in 0..=64u8 { | ||
| let mask = low_mask(bit); | ||
| let values: [u64; 32] = | ||
| std::array::from_fn(|i| (i as u64).wrapping_mul(0x9E37_79B9_7F4A_7C15) & mask); | ||
|
|
||
| let mut packed = vec![0u32; bit as usize]; | ||
| pack_wide(&values, 0, &mut packed, 0, bit); | ||
|
|
||
| let mut back = vec![0u64; 32]; | ||
| unpack_wide(&packed, 0, &mut back, 0, bit); | ||
|
|
||
| assert_eq!(values.to_vec(), back, "roundtrip mismatch at bit={bit}"); | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.