use std::hash::Hasher;
use std::io::{Read, Seek, SeekFrom, Write};
use crate::utils::{bitpack_u32, Packed};
use bitpacking::{BitPacker, BitPacker8x};
use rayon::prelude::*;
use twox_hash::{XxHash64, Xxh3Hash64};
const DEFAULT_CHUNK_SIZE: u64 = 2048;
const MULTITHREAD_BOUNDARY: usize = 8 * 1024 * 1024;
#[derive(PartialEq, Eq, Clone, Copy, bincode::Encode, bincode::Decode, Debug)]
pub enum Hashes {
XxHash64,
Xxh3Hash64,
}
impl Hashes {
pub fn hash(&self, id: &str) -> u64 {
match self {
Hashes::XxHash64 => {
let mut hasher = XxHash64::with_seed(42);
hasher.write(id.as_bytes());
hasher.finish()
}
Hashes::Xxh3Hash64 => {
let mut hasher = Xxh3Hash64::with_seed(42);
hasher.write(id.as_bytes());
hasher.finish()
}
}
}
}
pub struct DualIndexBuilder {
pub ids: Vec<std::sync::Arc<String>>,
pub locs: Vec<u32>,
pub chunk_size: u64,
pub hasher: Hashes,
}
impl Default for DualIndexBuilder {
fn default() -> Self {
Self {
ids: Vec::new(),
locs: Vec::new(),
chunk_size: DEFAULT_CHUNK_SIZE,
hasher: Hashes::XxHash64,
}
}
}
impl DualIndexBuilder {
pub fn new() -> Self {
DualIndexBuilder::default()
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
ids: Vec::with_capacity(capacity),
locs: Vec::with_capacity(capacity),
chunk_size: DEFAULT_CHUNK_SIZE,
hasher: Hashes::XxHash64,
}
}
pub fn with_hash(mut self, hash: Hashes) -> Self {
self.hasher = hash;
self
}
pub fn with_chunk_size(mut self, chunk_size: u64) -> Self {
self.chunk_size = chunk_size;
self
}
pub fn add(&mut self, id: std::sync::Arc<String>, loc: u32) {
self.ids.push(id);
self.locs.push(loc);
}
}
pub struct DualIndexWriter {
pub chunk_size: u64,
pub hasher: Hashes,
pub hashes: Vec<u64>,
pub ids: Vec<String>,
pub locs: Vec<u32>, }
impl From<DualIndexBuilder> for DualIndexWriter {
fn from(builder: DualIndexBuilder) -> Self {
let len = builder.ids.len();
assert!(len <= u32::MAX as usize, "u32::MAX is the maximum number of sequences. This can be addressed if necessary, please contact Joseph directly to discuss.");
let DualIndexBuilder {
ids,
locs,
chunk_size,
hasher,
} = builder;
let mut writer = DualIndexWriter {
hasher,
chunk_size,
hashes: Vec::with_capacity(len),
ids: Vec::with_capacity(len),
locs: Vec::with_capacity(len),
};
let hashes = if len >= MULTITHREAD_BOUNDARY {
ids.par_iter()
.map(|id| writer.hasher.hash(id))
.collect::<Vec<u64>>()
} else {
ids.iter()
.map(|id| writer.hasher.hash(id))
.collect::<Vec<u64>>()
};
let mut tuples: Vec<(u64, u32)> = izip!(hashes, locs).collect();
if len >= MULTITHREAD_BOUNDARY {
tuples.par_sort_unstable_by(|a, b| a.0.cmp(&b.0));
} else {
tuples.sort_unstable_by(|a, b| a.0.cmp(&b.0));
}
let mut hashes: Vec<u64> = Vec::with_capacity(len);
let mut locs: Vec<u32> = Vec::with_capacity(len);
for (hash, loc) in tuples.into_iter() {
hashes.push(hash);
locs.push(loc);
}
writer.hashes = hashes;
writer.locs = locs;
writer
}
}
impl DualIndexWriter {
pub fn write_to_buffer<W>(&mut self, mut out_buf: &mut W)
where
W: Write + Seek,
{
let bincode_config = bincode::config::standard()
.with_fixed_int_encoding()
.with_limit::<{ 64 * 1024 * 1024 }>();
let len = self.hashes.len();
let chunks = (len as f64 / (self.chunk_size as usize) as f64).ceil() as usize;
let chunk_size = self.chunk_size as usize;
let hash_index: Vec<u64> = (0..chunks).map(|i| self.hashes[i * chunk_size]).collect();
let header_loc = out_buf.seek(SeekFrom::Current(0)).unwrap();
bincode::encode_into_std_write(&len, &mut out_buf, bincode_config).unwrap();
bincode::encode_into_std_write(&self.hasher, &mut out_buf, bincode_config).unwrap();
bincode::encode_into_std_write(&self.chunk_size, &mut out_buf, bincode_config).unwrap(); bincode::encode_into_std_write(&(0_u64), &mut out_buf, bincode_config).unwrap(); bincode::encode_into_std_write(&(0_u64), &mut out_buf, bincode_config).unwrap(); bincode::encode_into_std_write(&(0_u8), &mut out_buf, bincode_config).unwrap(); bincode::encode_into_std_write(&(0_u64), &mut out_buf, bincode_config).unwrap(); bincode::encode_into_std_write(&(0_u64), &mut out_buf, bincode_config).unwrap();
let hash_index_location = out_buf.seek(SeekFrom::Current(0)).unwrap();
let mut hash_block_index = hash_index
.iter()
.map(|x| (*x, 0))
.collect::<Vec<(u64, u64)>>();
bincode::encode_into_std_write(&hash_block_index, &mut out_buf, bincode_config)
.expect("Bincode error");
let start = out_buf.seek(SeekFrom::Current(0)).unwrap();
(0..chunks).for_each(|i| {
let start = i * chunk_size;
let end = std::cmp::min((i + 1) * chunk_size, len);
hash_block_index[i].1 = out_buf.seek(SeekFrom::Current(0)).unwrap();
bincode::encode_into_std_write(
&self.hashes[start..end].to_vec(),
&mut out_buf,
bincode_config,
)
.expect("Bincode error");
});
let end = out_buf.seek(SeekFrom::Current(0)).unwrap();
log::info!("DEBUG: Hashes: {} bytes", end - start);
let (num_bits, bitpacked) = self.bitpack();
let bitpacked_location = out_buf.seek(SeekFrom::Current(0)).unwrap();
let mut bitpacked_len = 0;
let mut remainder_loc: u64 = 0;
for bp in bitpacked.into_iter() {
remainder_loc = out_buf.seek(SeekFrom::Current(0)).unwrap();
let len = bincode::encode_into_std_write(&bp, &mut out_buf, bincode_config)
.expect("Bincode error");
if bitpacked_len == 0 && bp.is_packed() {
bitpacked_len = len;
} else if bp.is_packed() {
assert_eq!(bitpacked_len, len);
}
}
let end = out_buf.seek(SeekFrom::Current(0)).unwrap();
out_buf.seek(SeekFrom::Start(hash_index_location)).unwrap();
bincode::encode_into_std_write(&hash_block_index, &mut out_buf, bincode_config)
.expect("Bincode error");
out_buf.seek(SeekFrom::Start(end)).unwrap();
let end = out_buf.seek(SeekFrom::Current(0)).unwrap();
out_buf.seek(SeekFrom::Start(header_loc)).unwrap();
bincode::encode_into_std_write(&len, &mut out_buf, bincode_config).unwrap();
bincode::encode_into_std_write(&self.hasher, &mut out_buf, bincode_config).unwrap();
bincode::encode_into_std_write(&self.chunk_size, &mut out_buf, bincode_config).unwrap(); bincode::encode_into_std_write(&hash_index_location, &mut out_buf, bincode_config).unwrap(); bincode::encode_into_std_write(&bitpacked_location, &mut out_buf, bincode_config).unwrap(); bincode::encode_into_std_write(&num_bits, &mut out_buf, bincode_config).unwrap(); bincode::encode_into_std_write(&bitpacked_len, &mut out_buf, bincode_config).unwrap(); bincode::encode_into_std_write(&remainder_loc, &mut out_buf, bincode_config).unwrap();
out_buf.seek(SeekFrom::Start(end)).unwrap();
}
fn bitpack(&self) -> (u8, Vec<Packed>) {
bitpack_u32(&self.locs)
}
}
#[derive(Debug)]
pub struct DualIndex {
pub idx_start: u64,
pub chunk_size: u64,
pub hash_index: u64,
pub bitpacked_loc: u64,
pub num_bits: u8,
pub bitpacked_len: u64,
pub remainder_loc: u64,
pub value_block_index: Option<Vec<(u64, u64)>>,
pub hasher: Hashes,
pub len: u64,
}
impl DualIndex {
pub fn new<R>(mut in_buf: &mut R, idx_start: u64) -> Result<Self, String>
where
R: Read + Seek,
{
let bincode_config = bincode::config::standard().with_fixed_int_encoding();
in_buf
.seek(SeekFrom::Start(idx_start))
.expect(format!("Unable to seek for DualIndex: {}", idx_start).as_str());
let len: u64 = match bincode::decode_from_std_read(&mut in_buf, bincode_config) {
Ok(x) => x,
Err(e) => {
return Err(format!("Unable to decode len: {}", e));
}
};
let hasher: Hashes = match bincode::decode_from_std_read(&mut in_buf, bincode_config) {
Ok(x) => x,
Err(e) => {
return Err(format!("Unable to decode hasher: {}", e));
}
};
let chunk_size: u64 = match bincode::decode_from_std_read(&mut in_buf, bincode_config) {
Ok(x) => x,
Err(e) => {
return Err(format!("Unable to decode chunk_size: {}", e));
}
};
let hash_index = match bincode::decode_from_std_read(&mut in_buf, bincode_config) {
Ok(x) => x,
Err(e) => {
return Err(format!("Unable to decode hash_index: {}", e));
}
};
let bitpacked_loc = match bincode::decode_from_std_read(&mut in_buf, bincode_config) {
Ok(x) => x,
Err(e) => {
return Err(format!("Unable to decode bitpacked_loc: {}", e));
}
};
let num_bits = match bincode::decode_from_std_read(&mut in_buf, bincode_config) {
Ok(x) => x,
Err(e) => {
return Err(format!("Unable to decode num_bits: {}", e));
}
};
let bitpacked_len = match bincode::decode_from_std_read(&mut in_buf, bincode_config) {
Ok(x) => x,
Err(e) => {
return Err(format!("Unable to decode bitpacked_len: {}", e));
}
};
let remainder_loc = match bincode::decode_from_std_read(&mut in_buf, bincode_config) {
Ok(x) => x,
Err(e) => {
return Err(format!("Unable to decode remainder_loc: {}", e));
}
};
Ok(DualIndex {
idx_start,
chunk_size,
hash_index,
bitpacked_loc,
num_bits,
bitpacked_len,
remainder_loc,
value_block_index: None,
hasher,
len,
})
}
pub fn load_index<R>(&mut self, mut in_buf: &mut R)
where
R: Read + Seek,
{
let bincode_config = bincode::config::standard().with_fixed_int_encoding();
in_buf.seek(SeekFrom::Start(self.hash_index)).unwrap();
let hash_block_index: Vec<(u64, u64)> =
bincode::decode_from_std_read(&mut in_buf, bincode_config).unwrap();
self.value_block_index = Some(hash_block_index);
}
fn get_putative_block(&self, hash: u64) -> Option<(usize, u64)> {
let vbi = self.value_block_index.as_ref().unwrap();
if vbi.is_empty() {
return Some((0, vbi[0].1));
}
let find = vbi.binary_search_by(|&(h, _)| {
h.cmp(&hash)
});
match find {
Ok(i) => Some((i, vbi[i].1)),
Err(i) => {
if i == 0 {
Some((0, vbi[0].1))
} else {
Some((i - 1, vbi[i - 1].1))
}
}
}
}
fn get_hash_chunk_by_loc<R>(&self, mut in_buf: R, loc: u64) -> Vec<u64>
where
R: Read + Seek,
{
let bincode_config = bincode::config::standard().with_fixed_int_encoding();
in_buf.seek(SeekFrom::Start(loc)).unwrap();
bincode::decode_from_std_read(&mut in_buf, bincode_config).unwrap()
}
pub fn get_bitpacked_chunk_by_pos<R>(&self, mut in_buf: R, pos: usize) -> Packed
where
R: Read + Seek,
{
let bincode_config = bincode::config::standard().with_fixed_int_encoding();
let chunk = (pos / BitPacker8x::BLOCK_LEN) as u64;
let bitpacked_loc = self.bitpacked_loc as u64 + (chunk * self.bitpacked_len);
if bitpacked_loc > self.remainder_loc {
in_buf.seek(SeekFrom::Start(self.remainder_loc)).unwrap();
} else {
in_buf.seek(SeekFrom::Start(bitpacked_loc)).unwrap();
}
bincode::decode_from_std_read(&mut in_buf, bincode_config).unwrap()
}
pub fn len(&self) -> usize {
self.len as usize
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn find<R>(&mut self, mut in_buf: &mut R, id: &str) -> Option<u32>
where
R: Read + Seek,
{
if self.value_block_index.is_none() {
self.load_index(&mut in_buf);
}
let hash = self.hasher.hash(id);
let putative_block = self.get_putative_block(hash);
putative_block?;
let (block_num, hash_chunk_loc) = putative_block.unwrap();
let hash_chunk = self.get_hash_chunk_by_loc(&mut in_buf, hash_chunk_loc);
let pos = hash_chunk.binary_search(&hash);
if pos.is_err() {
return None;
}
let actual_loc = pos.unwrap() as usize + block_num * self.chunk_size as usize;
let bp = self.get_bitpacked_chunk_by_pos(&mut in_buf, actual_loc);
let value = bp.unpack(self.num_bits).unwrap();
let bitpacked_pos = actual_loc % BitPacker8x::BLOCK_LEN;
Some(value[bitpacked_pos])
}
}
#[cfg(test)]
mod tests {
use super::*;
use rand::Rng;
use std::io::Cursor;
use std::sync::Arc;
#[test]
pub fn test_dual_index_builder() {
let mut out_buf: Cursor<Vec<u8>> = Cursor::new(Vec::new());
let mut di = DualIndexBuilder::new();
let mut rng = rand::thread_rng();
for i in 0_u64..10000 {
di.add(Arc::new(i.to_string()), (i * 2) as u32);
}
for i in (0_u64..10000).step_by(2) {
di.add(Arc::new(i.to_string()), rng.gen::<u32>());
}
di.add(Arc::new("Max".to_string()), std::u32::MAX);
for i in (0_u64..1000).step_by(2) {
di.add(Arc::new(i.to_string()), rng.gen::<u32>());
}
let mut writer: DualIndexWriter = di.into();
writer.write_to_buffer(&mut out_buf);
println!("{:#?}", out_buf.into_inner().len());
}
#[test]
pub fn test_dual_index_reader() {
let mut out_buf: Cursor<Vec<u8>> = Cursor::new(Vec::new());
let mut di = DualIndexBuilder::new();
let mut rng = rand::thread_rng();
for i in 0_u64..10000 {
di.add(Arc::new(i.to_string()), rng.gen::<u32>());
}
di.add(Arc::new("Max".to_string()), std::u32::MAX);
let mut writer: DualIndexWriter = di.into();
writer.write_to_buffer(&mut out_buf);
drop(writer);
let mut in_buf = Cursor::new(out_buf.into_inner());
let mut reader = DualIndex::new(&mut in_buf, 0).unwrap();
assert_eq!(Some(std::u32::MAX), reader.find(&mut in_buf, "Max"));
}
}