#[cfg(target_arch = "aarch64")]
use core::arch::asm;
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::{_MM_HINT_T0, _mm_prefetch};
use std::{
fmt,
hint::spin_loop,
iter::Chain,
mem::size_of,
ops::{Deref, DerefMut, Index},
slice::{Iter, IterMut, from_raw_parts, from_raw_parts_mut},
sync::atomic::{Ordering, fence},
thread::{sleep, yield_now},
time::Duration,
};
use whasher::fast_hash;
use wram::{DirectVirtualMemory, DirectVmBlock};
use crate::{
Result,
bucket::{BucketExclusiveGuard, BucketSharedGuard, DATA_ENTRIES, HashBucket},
entry::HashBucketEntry,
error::Error,
overflow_pool::OverflowPool,
};
enum ChainStep {
Next,
End,
Cycle,
}
const MAX_CHAIN_STEPS: usize = 1 << 20;
struct ChainWalker<'a> {
curr: &'a HashBucket,
step: usize,
}
impl<'a> ChainWalker<'a> {
#[inline]
fn new(start: &'a HashBucket) -> Self {
Self {
curr: start,
step: 0,
}
}
#[inline]
fn advance(&mut self, pool: &'a OverflowPool) -> ChainStep {
let overflow_idx = self.curr.overflow_index();
if overflow_idx == 0 {
return ChainStep::End;
}
self.curr = unsafe { pool.get_unchecked(overflow_idx) };
self.step += 1;
if self.step >= MAX_CHAIN_STEPS {
ChainStep::Cycle
} else {
ChainStep::Next
}
}
}
#[derive(Debug)]
pub struct HashEntryInfo<'a> {
pub(crate) bucket: &'a HashBucket,
pub(crate) slot: usize,
pub(crate) raw: u64,
pub(crate) tag: u16,
}
impl<'a> HashEntryInfo<'a> {
#[inline]
pub fn is_found(&self) -> bool {
self.raw != 0
}
#[inline]
pub fn address(&self) -> u64 {
HashBucketEntry::from_raw(self.raw).address()
}
#[inline]
pub fn try_cas(&mut self, new_address: u64) -> bool {
if new_address == HashBucketEntry::INVALID_ADDRESS
|| new_address > HashBucketEntry::ADDRESS_MASK
{
return false;
}
debug_assert!(self.slot < DATA_ENTRIES, "try_cas 槽位越界");
let new_entry = HashBucketEntry::new(new_address, self.tag, false);
let new_raw = new_entry.as_raw();
if unsafe { self.bucket.entries.get_unchecked(self.slot) }
.compare_exchange(self.raw, new_raw, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
self.raw = new_raw;
true
} else {
false
}
}
#[inline]
pub fn try_elide(&mut self) -> bool {
if !self.is_found() || self.slot >= DATA_ENTRIES {
return false;
}
debug_assert!(self.slot < DATA_ENTRIES);
if unsafe { self.bucket.entries.get_unchecked(self.slot) }
.compare_exchange(self.raw, 0, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
self.raw = 0;
true
} else {
false
}
}
}
#[derive(Debug, Clone)]
pub struct CandidateAddresses {
buf: [u64; 8],
len: u8,
extra: Vec<u64>,
}
impl Default for CandidateAddresses {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl CandidateAddresses {
#[inline]
pub const fn new() -> Self {
Self {
buf: [0; 8],
len: 0,
extra: Vec::new(),
}
}
#[inline]
pub fn push(&mut self, addr: u64) {
if (self.len as usize) < self.buf.len() {
self.buf[self.len as usize] = addr;
self.len += 1;
} else {
self.extra.push(addr);
}
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len == 0
}
#[inline]
pub fn len(&self) -> usize {
(self.len as usize) + self.extra.len()
}
#[inline]
pub fn first(&self) -> Option<u64> {
if self.len > 0 {
Some(self.buf[0])
} else {
self.extra.first().copied()
}
}
#[inline]
pub fn iter(&self) -> impl DoubleEndedIterator<Item = &u64> {
self.buf[..self.len as usize]
.iter()
.chain(self.extra.iter())
}
#[inline]
pub fn contains(&self, addr: u64) -> bool {
self.buf[..self.len as usize].contains(&addr) || self.extra.contains(&addr)
}
#[inline]
pub fn as_slice(&self) -> Option<&[u64]> {
if self.extra.is_empty() {
Some(&self.buf[..self.len as usize])
} else {
None
}
}
#[inline]
pub fn is_heap_allocated(&self) -> bool {
!self.extra.is_empty()
}
#[inline]
pub fn retain<F: FnMut(u64) -> bool>(&mut self, mut f: F) {
let len = self.len as usize;
let mut new_len = 0;
for i in 0..len {
let val = self.buf[i];
if f(val) {
self.buf[new_len] = val;
new_len += 1;
}
}
self.len = new_len as u8;
self.extra.retain(|&x| f(x));
let available = self.buf.len() - (self.len as usize);
if available > 0 && !self.extra.is_empty() {
let take = available.min(self.extra.len());
let start = self.len as usize;
self.buf[start..start + take].copy_from_slice(&self.extra[..take]);
self.extra.drain(..take);
self.len += take as u8;
}
}
#[inline]
pub fn sort_descending(&mut self) {
if self.extra.is_empty() {
let len = self.len as usize;
match len {
0 | 1 => {}
2 => {
if self.buf[0] < self.buf[1] {
self.buf.swap(0, 1);
}
}
_ => {
self.buf[..len].sort_unstable_by(|a, b| b.cmp(a));
}
}
} else {
let buf_len = self.len as usize;
self.extra.reserve(buf_len);
self.extra.extend_from_slice(&self.buf[..buf_len]);
self.extra.sort_unstable_by(|a, b| b.cmp(a));
let main_len = self.buf.len().min(self.extra.len());
self.buf[..main_len].copy_from_slice(&self.extra[..main_len]);
self.extra.drain(..main_len);
self.len = main_len as u8;
}
}
pub fn to_vec(&self) -> Vec<u64> {
let mut v = Vec::with_capacity(self.len());
v.extend_from_slice(&self.buf[..self.len as usize]);
v.extend_from_slice(&self.extra);
v
}
}
impl Index<usize> for CandidateAddresses {
type Output = u64;
#[inline]
fn index(&self, idx: usize) -> &Self::Output {
let len = self.len as usize;
if idx < len {
&self.buf[idx]
} else {
&self.extra[idx - len]
}
}
}
impl PartialEq for CandidateAddresses {
fn eq(&self, other: &Self) -> bool {
self.len() == other.len() && self.iter().zip(other.iter()).all(|(a, b)| a == b)
}
}
impl Eq for CandidateAddresses {}
pub struct CandidateAddressesIntoIter {
candidates: CandidateAddresses,
start: usize,
end: usize,
}
impl Iterator for CandidateAddressesIntoIter {
type Item = u64;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.start >= self.end {
return None;
}
let val = self.candidates[self.start];
self.start += 1;
Some(val)
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let rem = self.end - self.start;
(rem, Some(rem))
}
}
impl DoubleEndedIterator for CandidateAddressesIntoIter {
#[inline]
fn next_back(&mut self) -> Option<Self::Item> {
if self.start >= self.end {
return None;
}
self.end -= 1;
Some(self.candidates[self.end])
}
}
impl ExactSizeIterator for CandidateAddressesIntoIter {}
impl IntoIterator for CandidateAddresses {
type Item = u64;
type IntoIter = CandidateAddressesIntoIter;
#[inline]
fn into_iter(self) -> Self::IntoIter {
let end = self.len();
CandidateAddressesIntoIter {
candidates: self,
start: 0,
end,
}
}
}
impl<'a> IntoIterator for &'a CandidateAddresses {
type Item = &'a u64;
type IntoIter = Chain<Iter<'a, u64>, Iter<'a, u64>>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
self.buf[..self.len as usize]
.iter()
.chain(self.extra.iter())
}
}
pub struct HashBuckets {
block: DirectVmBlock,
len: usize,
}
impl HashBuckets {
pub fn new(len: usize) -> Result<Self> {
if len == 0 {
return Err(Error::InvalidBucketCount(0));
}
let size_bytes = len
.checked_mul(size_of::<HashBucket>())
.ok_or(Error::InvalidBucketCount(len))?;
let block = DirectVirtualMemory::allocate(size_bytes, 64)?;
Ok(Self { block, len })
}
#[inline(always)]
pub const fn len(&self) -> usize {
self.len
}
#[inline(always)]
pub const fn is_empty(&self) -> bool {
self.len == 0
}
#[inline(always)]
pub fn as_slice(&self) -> &[HashBucket] {
if self.len == 0 {
&[]
} else {
unsafe { from_raw_parts(self.block.aligned_ptr as *const HashBucket, self.len) }
}
}
#[inline(always)]
pub fn as_mut_slice(&mut self) -> &mut [HashBucket] {
if self.len == 0 {
&mut []
} else {
unsafe { from_raw_parts_mut(self.block.aligned_ptr as *mut HashBucket, self.len) }
}
}
#[inline(always)]
pub fn as_ptr(&self) -> *const HashBucket {
self.block.aligned_ptr as *const HashBucket
}
#[inline(always)]
pub fn as_mut_ptr(&mut self) -> *mut HashBucket {
self.block.aligned_ptr as *mut HashBucket
}
#[inline(always)]
pub unsafe fn get_unchecked(&self, index: usize) -> &HashBucket {
unsafe { &*(self.block.aligned_ptr as *const HashBucket).add(index) }
}
}
impl Deref for HashBuckets {
type Target = [HashBucket];
#[inline(always)]
fn deref(&self) -> &Self::Target {
self.as_slice()
}
}
impl DerefMut for HashBuckets {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Self::Target {
self.as_mut_slice()
}
}
impl AsRef<[HashBucket]> for HashBuckets {
#[inline(always)]
fn as_ref(&self) -> &[HashBucket] {
self.as_slice()
}
}
impl AsMut<[HashBucket]> for HashBuckets {
#[inline(always)]
fn as_mut(&mut self) -> &mut [HashBucket] {
self.as_mut_slice()
}
}
impl<'a> IntoIterator for &'a HashBuckets {
type Item = &'a HashBucket;
type IntoIter = Iter<'a, HashBucket>;
#[inline(always)]
fn into_iter(self) -> Self::IntoIter {
self.as_slice().iter()
}
}
impl<'a> IntoIterator for &'a mut HashBuckets {
type Item = &'a mut HashBucket;
type IntoIter = IterMut<'a, HashBucket>;
#[inline(always)]
fn into_iter(self) -> Self::IntoIter {
self.as_mut_slice().iter_mut()
}
}
impl fmt::Debug for HashBuckets {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("HashBuckets")
.field("len", &self.len)
.field("aligned_ptr", &self.block.aligned_ptr)
.finish()
}
}
pub struct HashIndex {
pub buckets: HashBuckets,
pub overflow_pool: OverflowPool,
pub size: usize,
pub mask: usize,
}
impl HashIndex {
pub const PREFETCH_WINDOW: usize = 12;
pub const BATCH_CHUNK_SIZE: usize = 64;
pub const INLINE_LOCK_ENTRIES: usize = 16;
pub const SPIN_RETRY_THRESHOLD: usize = 32;
pub const SPIN_LIMIT_MAX_EXP: usize = 5;
pub const SPIN_LIMIT_JITTER_MASK: usize = 0x7;
pub const YIELD_RETRY_BUDGET: usize = 1024;
pub const SLEEP_RETRY_BUDGET: usize = 16384;
pub const SLEEP_BASE_MICROS: u64 = 100;
pub const SLEEP_MAX_ADDITIONAL_MICROS: u64 = 900;
pub fn new(num_buckets: usize) -> Result<Self> {
if num_buckets == 0 || !num_buckets.is_power_of_two() {
return Err(Error::InvalidBucketCount(num_buckets));
}
let buckets = HashBuckets::new(num_buckets)?;
Ok(Self {
buckets,
overflow_pool: OverflowPool::new(),
size: num_buckets,
mask: num_buckets - 1,
})
}
#[inline(always)]
pub fn get_bucket(&self, bucket_idx: usize) -> &HashBucket {
let idx = bucket_idx & self.mask;
unsafe { self.buckets.get_unchecked(idx) }
}
#[inline]
pub fn hash_key(key: &[u8]) -> u64 {
fast_hash(key)
}
#[inline]
pub fn find_tag(&self, key: &[u8]) -> Option<u64> {
let hash = Self::hash_key(key);
self.find_tag_by_hash(hash)
}
#[inline]
pub fn find_tag_by_hash(&self, hash: u64) -> Option<u64> {
let tag = HashBucketEntry::tag_from_hash(hash);
let bucket_idx = (hash as usize) & self.mask;
let mut walker = ChainWalker::new(self.get_bucket(bucket_idx));
loop {
if let Some(addr) = walker.curr.find_tag_address(tag) {
return Some(addr);
}
match walker.advance(&self.overflow_pool) {
ChainStep::Next => {}
ChainStep::End | ChainStep::Cycle => return None,
}
}
}
#[inline]
pub fn lookup_candidates(&self, key: &[u8]) -> CandidateAddresses {
self.lookup_candidates_by_hash(Self::hash_key(key))
}
pub fn lookup_candidates_by_hash(&self, hash: u64) -> CandidateAddresses {
let tag = HashBucketEntry::tag_from_hash(hash);
let bucket_idx = (hash as usize) & self.mask;
let mut results = CandidateAddresses::new();
let mut walker = ChainWalker::new(self.get_bucket(bucket_idx));
let expected_hi = (tag as u64) & HashBucketEntry::TAG_MASK;
loop {
for item in &walker.curr.entries[..DATA_ENTRIES] {
let raw = item.load(Ordering::Relaxed);
if (raw >> HashBucketEntry::TAG_SHIFT) == expected_hi {
let addr = raw & HashBucketEntry::ADDRESS_MASK;
if addr != 0 {
results.push(addr);
}
}
}
match walker.advance(&self.overflow_pool) {
ChainStep::Next => {}
ChainStep::End | ChainStep::Cycle => break,
}
}
if !results.is_empty() {
fence(Ordering::Acquire);
}
results
}
#[inline]
pub fn lookup(&self, key: &[u8]) -> Vec<u64> {
self.lookup_candidates(key).to_vec()
}
#[inline]
pub fn lookup_by_hash(&self, hash: u64) -> Vec<u64> {
self.lookup_candidates_by_hash(hash).to_vec()
}
#[inline]
pub fn insert(&self, key: &[u8], address: u64) -> Result<()> {
self.insert_by_hash(Self::hash_key(key), address)
}
pub fn insert_by_hash(&self, hash: u64, address: u64) -> Result<()> {
if address == HashBucketEntry::INVALID_ADDRESS {
return Err(Error::InvalidAddress(address));
}
if address > HashBucketEntry::ADDRESS_MASK {
return Err(Error::AddressOverflow(address));
}
let tag = HashBucketEntry::tag_from_hash(hash);
let bucket_idx = (hash as usize) & self.mask;
'retry: loop {
let mut walker = ChainWalker::new(self.get_bucket(bucket_idx));
loop {
if let Some(slot) = walker.curr.find_empty_slot() {
if walker.curr.try_insert(slot, tag, address) {
return Ok(());
}
continue 'retry;
}
match walker.advance(&self.overflow_pool) {
ChainStep::Next => {}
ChainStep::End => {
if walker.curr.overflow_index() != 0 {
match walker.advance(&self.overflow_pool) {
ChainStep::Next => {}
ChainStep::Cycle => return Err(Error::OverflowCycleDetected),
ChainStep::End => return Err(Error::OverflowPoolExhausted),
}
} else {
let new_overflow_idx = self.overflow_pool.allocate()?;
if walker.curr.set_overflow_index(new_overflow_idx) {
match walker.advance(&self.overflow_pool) {
ChainStep::Next => {}
_ => return Err(Error::OverflowPoolExhausted),
}
} else {
self.overflow_pool.free(new_overflow_idx);
match walker.advance(&self.overflow_pool) {
ChainStep::Next => {}
_ => return Err(Error::OverflowPoolExhausted),
}
}
}
}
ChainStep::Cycle => return Err(Error::OverflowCycleDetected),
}
}
}
}
pub fn find_tag_or_insert_by_hash(&self, hash: u64, address: u64) -> Result<(Option<u64>, bool)> {
if address == HashBucketEntry::INVALID_ADDRESS {
return Err(Error::InvalidAddress(address));
}
if address > HashBucketEntry::ADDRESS_MASK {
return Err(Error::AddressOverflow(address));
}
let mut spins = 0usize;
loop {
let mut hei = self.find_or_create_tag_by_hash(hash)?;
if hei.is_found() {
return Ok((Some(hei.address()), false));
}
if hei.try_cas(address) {
return Ok((None, true));
}
spins += 1;
if spins < Self::SPIN_RETRY_THRESHOLD {
spin_loop();
} else {
yield_now();
}
}
}
#[inline]
pub fn find_tag_or_insert(&self, key: &[u8], address: u64) -> Result<(Option<u64>, bool)> {
self.find_tag_or_insert_by_hash(Self::hash_key(key), address)
}
#[inline]
pub fn find_or_create_tag(&self, key: &[u8]) -> Result<HashEntryInfo<'_>> {
self.find_or_create_tag_with_min_addr(key, 0)
}
#[inline]
pub fn find_or_create_tag_with_min_addr(
&self,
key: &[u8],
min_valid_addr: u64,
) -> Result<HashEntryInfo<'_>> {
self.find_or_create_tag_by_hash_with_min_addr(Self::hash_key(key), min_valid_addr)
}
#[inline]
pub fn find_or_create_tag_by_hash(&self, hash: u64) -> Result<HashEntryInfo<'_>> {
self.find_or_create_tag_by_hash_with_min_addr(hash, 0)
}
pub fn find_or_create_tag_by_hash_with_min_addr(
&self,
hash: u64,
min_valid_addr: u64,
) -> Result<HashEntryInfo<'_>> {
let tag = HashBucketEntry::tag_from_hash(hash);
let bucket_idx = (hash as usize) & self.mask;
let mut walker = ChainWalker::new(self.get_bucket(bucket_idx));
let mut first_free: Option<(&HashBucket, usize)> = None;
'search: loop {
for (slot, item) in walker.curr.entries[..DATA_ENTRIES].iter().enumerate() {
let raw = item.load(Ordering::Relaxed);
if raw == 0 {
first_free.get_or_insert((walker.curr, slot));
continue;
}
let entry = HashBucketEntry::from_raw(raw);
if min_valid_addr > 0
&& entry.is_valid()
&& !entry.is_read_cache()
&& entry.address() < min_valid_addr
{
match item.compare_exchange(raw, 0, Ordering::AcqRel, Ordering::Acquire) {
Ok(_) => {
first_free.get_or_insert((walker.curr, slot));
}
Err(actual_raw) => {
if actual_raw == 0 {
first_free.get_or_insert((walker.curr, slot));
continue;
}
let actual = HashBucketEntry::from_raw(actual_raw);
if actual.matches_tag(tag)
&& (actual.is_read_cache() || actual.address() >= min_valid_addr)
{
fence(Ordering::Acquire);
return Ok(HashEntryInfo {
bucket: walker.curr,
slot,
raw: actual_raw,
tag,
});
}
}
}
continue;
}
if entry.matches_tag(tag) {
fence(Ordering::Acquire);
return Ok(HashEntryInfo {
bucket: walker.curr,
slot,
raw,
tag,
});
}
}
match walker.advance(&self.overflow_pool) {
ChainStep::Next => {}
ChainStep::End => {
if walker.curr.overflow_index() != 0 {
match walker.advance(&self.overflow_pool) {
ChainStep::Next => continue 'search,
ChainStep::Cycle => return Err(Error::OverflowCycleDetected),
ChainStep::End => return Err(Error::OverflowPoolExhausted),
}
}
if let Some((free_bucket, slot)) = first_free {
return Ok(HashEntryInfo {
bucket: free_bucket,
slot,
raw: 0,
tag,
});
}
let new_overflow_idx = self.overflow_pool.allocate()?;
if walker.curr.set_overflow_index(new_overflow_idx) {
let new_bucket = unsafe { self.overflow_pool.get_unchecked(new_overflow_idx) };
return Ok(HashEntryInfo {
bucket: new_bucket,
slot: 0,
raw: 0,
tag,
});
}
self.overflow_pool.free(new_overflow_idx);
match walker.advance(&self.overflow_pool) {
ChainStep::Next => continue 'search,
_ => return Err(Error::OverflowPoolExhausted),
}
}
ChainStep::Cycle => return Err(Error::OverflowCycleDetected),
}
}
}
#[inline]
pub fn update_address(&self, key: &[u8], old_address: u64, new_address: u64) -> bool {
self.update_address_by_hash(Self::hash_key(key), old_address, new_address)
}
pub fn update_address_by_hash(&self, hash: u64, old_address: u64, new_address: u64) -> bool {
if new_address == HashBucketEntry::INVALID_ADDRESS
|| new_address > HashBucketEntry::ADDRESS_MASK
|| old_address == HashBucketEntry::INVALID_ADDRESS
{
return false;
}
let Some(mut hei) = self.find_exact_entry_by_hash(hash, old_address) else {
return false;
};
hei.try_cas(new_address)
}
#[inline]
pub fn delete(&self, key: &[u8], address: u64) -> bool {
self.delete_by_hash(Self::hash_key(key), address)
}
pub fn delete_by_hash(&self, hash: u64, address: u64) -> bool {
if address == HashBucketEntry::INVALID_ADDRESS {
return false;
}
let Some(mut hei) = self.find_exact_entry_by_hash(hash, address) else {
return false;
};
hei.try_elide()
}
fn find_exact_entry_by_hash(&self, hash: u64, address: u64) -> Option<HashEntryInfo<'_>> {
let tag = HashBucketEntry::tag_from_hash(hash);
let mut walker = ChainWalker::new(self.get_bucket((hash as usize) & self.mask));
loop {
if let Some((slot, entry)) = walker.curr.find_entry_by_address(tag, address) {
return Some(HashEntryInfo {
bucket: walker.curr,
slot,
raw: entry.as_raw(),
tag,
});
}
match walker.advance(&self.overflow_pool) {
ChainStep::Next => {}
ChainStep::End | ChainStep::Cycle => return None,
}
}
}
#[inline]
pub fn bucket_count(&self) -> usize {
self.size
}
#[inline]
pub fn overflow_bucket_count(&self) -> u64 {
self.overflow_pool.allocated_count()
}
#[inline]
pub fn bucket(&self, bucket_idx: usize) -> &HashBucket {
self.get_bucket(bucket_idx)
}
#[inline]
pub fn bucket_for_key(&self, key: &[u8]) -> &HashBucket {
let hash = Self::hash_key(key);
self.get_bucket((hash as usize) & self.mask)
}
#[inline]
pub fn bucket_index_for_hash(&self, hash: u64) -> usize {
(hash as usize) & self.mask
}
#[inline]
pub fn bucket_index_for_key(&self, key: &[u8]) -> usize {
let hash = Self::hash_key(key);
(hash as usize) & self.mask
}
#[inline]
pub fn try_lock_shared(&self, key: &[u8]) -> bool {
self.bucket_for_key(key).try_lock_shared()
}
#[inline]
pub fn unlock_shared(&self, key: &[u8]) {
self.bucket_for_key(key).unlock_shared();
}
#[inline]
pub fn try_lock_exclusive(&self, key: &[u8]) -> bool {
self.bucket_for_key(key).try_lock_exclusive()
}
#[inline]
pub fn unlock_exclusive(&self, key: &[u8]) {
self.bucket_for_key(key).unlock_exclusive();
}
#[inline]
pub fn downgrade(&self, key: &[u8]) {
self.bucket_for_key(key).downgrade_latch();
}
#[inline]
pub fn is_locked(&self, key: &[u8]) -> bool {
self.bucket_for_key(key).is_latched()
}
#[inline]
pub fn lock_shared_guard(&self, key: &[u8]) -> Option<BucketSharedGuard<'_>> {
self.bucket_for_key(key).lock_shared_guard()
}
#[inline]
pub fn lock_exclusive_guard(&self, key: &[u8]) -> Option<BucketExclusiveGuard<'_>> {
self.bucket_for_key(key).lock_exclusive_guard()
}
pub fn lookup_candidates_batch(&self, keys: &[&[u8]], results: &mut [CandidateAddresses]) {
let count = keys.len().min(results.len());
if count == 0 {
return;
}
let mut hashes_buf = [0u64; Self::BATCH_CHUNK_SIZE];
for (keys_chunk, results_chunk) in keys[..count]
.chunks(Self::BATCH_CHUNK_SIZE)
.zip(results[..count].chunks_mut(Self::BATCH_CHUNK_SIZE))
{
let chunk_len = keys_chunk.len();
for (slot, &k) in hashes_buf[..chunk_len].iter_mut().zip(keys_chunk) {
*slot = Self::hash_key(k);
}
self.lookup_candidates_batch_by_hash(&hashes_buf[..chunk_len], results_chunk);
}
}
pub fn lookup_candidates_batch_by_hash(
&self,
hashes: &[u64],
results: &mut [CandidateAddresses],
) {
let count = hashes.len().min(results.len());
if count == 0 {
return;
}
let warmup_count = Self::PREFETCH_WINDOW.min(count);
for &hash in &hashes[..warmup_count] {
let bucket_idx = (hash as usize) & self.mask;
prefetch_read_l1(self.get_bucket(bucket_idx));
}
for (i, (&hash, res)) in hashes[..count]
.iter()
.zip(&mut results[..count])
.enumerate()
{
if let Some(&next_hash) = hashes.get(i + Self::PREFETCH_WINDOW) {
let bucket_idx = (next_hash as usize) & self.mask;
prefetch_read_l1(self.get_bucket(bucket_idx));
}
*res = self.lookup_candidates_by_hash(hash);
}
}
pub fn find_tag_batch_by_hash(&self, hashes: &[u64], results: &mut [Option<u64>]) {
let count = hashes.len().min(results.len());
if count == 0 {
return;
}
let warmup_count = Self::PREFETCH_WINDOW.min(count);
for &hash in &hashes[..warmup_count] {
let bucket_idx = (hash as usize) & self.mask;
prefetch_read_l1(self.get_bucket(bucket_idx));
}
for (i, (&hash, res)) in hashes[..count]
.iter()
.zip(&mut results[..count])
.enumerate()
{
if let Some(&next_hash) = hashes.get(i + Self::PREFETCH_WINDOW) {
let bucket_idx = (next_hash as usize) & self.mask;
prefetch_read_l1(self.get_bucket(bucket_idx));
}
*res = self.find_tag_by_hash(hash);
}
}
pub fn find_tag_batch(&self, keys: &[&[u8]], results: &mut [Option<u64>]) {
let count = keys.len().min(results.len());
if count == 0 {
return;
}
let mut hashes_buf = [0u64; Self::BATCH_CHUNK_SIZE];
for (keys_chunk, results_chunk) in keys[..count]
.chunks(Self::BATCH_CHUNK_SIZE)
.zip(results[..count].chunks_mut(Self::BATCH_CHUNK_SIZE))
{
let chunk_len = keys_chunk.len();
for (slot, &k) in hashes_buf[..chunk_len].iter_mut().zip(keys_chunk) {
*slot = Self::hash_key(k);
}
self.find_tag_batch_by_hash(&hashes_buf[..chunk_len], results_chunk);
}
}
#[inline]
fn in_place_dedup_by<T: Copy, F>(slice: &mut [T], mut same_bucket: F) -> usize
where
F: FnMut(&T, &T) -> bool,
{
if slice.len() <= 1 {
return slice.len();
}
let mut write_idx = 1;
for read_idx in 1..slice.len() {
if !same_bucket(&slice[write_idx - 1], &slice[read_idx]) {
if write_idx != read_idx {
slice[write_idx] = slice[read_idx];
}
write_idx += 1;
}
}
write_idx
}
fn acquire_bucket_locks<I>(&self, items: I) -> Result<MultiBucketGuard<'_>>
where
I: ExactSizeIterator<Item = (usize, bool)>,
{
let count = items.len();
if count == 0 {
return Ok(MultiBucketGuard::new(self));
}
let mut stack_entries = [(0usize, false); Self::INLINE_LOCK_ENTRIES];
let mut heap_entries;
let entries: &mut [(usize, bool)] = if count <= Self::INLINE_LOCK_ENTRIES {
for (slot, e) in stack_entries[..count].iter_mut().zip(items) {
*slot = e;
}
&mut stack_entries[..count]
} else {
heap_entries = items.collect::<Vec<_>>();
&mut heap_entries
};
entries.sort_unstable_by(|a, b| a.0.cmp(&b.0).then_with(|| b.1.cmp(&a.1)));
let deduped_len = Self::in_place_dedup_by(entries, |a, b| a.0 == b.0);
self.acquire_unique_locked_entries(&entries[..deduped_len])
}
#[inline]
pub fn acquire_keys_lock_exclusive(&self, keys: &[&[u8]]) -> Result<MultiBucketGuard<'_>> {
self.acquire_bucket_locks(keys.iter().map(|k| (self.bucket_index_for_key(k), true)))
}
#[inline]
pub fn acquire_hash_locks(&self, items: &[(u64, bool)]) -> Result<MultiBucketGuard<'_>> {
self.acquire_bucket_locks(
items
.iter()
.map(|&(h, ex)| (self.bucket_index_for_hash(h), ex)),
)
}
fn acquire_unique_locked_entries(
&self,
unique_entries: &[(usize, bool)],
) -> Result<MultiBucketGuard<'_>> {
let mut retry_count = 0usize;
loop {
let mut locked_count = 0usize;
for &(b_idx, is_exclusive) in unique_entries {
let bucket = unsafe { self.buckets.get_unchecked(b_idx) };
let ok = if is_exclusive {
bucket.try_lock_exclusive()
} else {
bucket.try_lock_shared()
};
if ok {
locked_count += 1;
} else {
break;
}
}
if locked_count == unique_entries.len() {
return Ok(MultiBucketGuard::from_slice(self, unique_entries));
}
for &(b_idx, is_exclusive) in unique_entries[..locked_count].iter().rev() {
let bucket = unsafe { self.buckets.get_unchecked(b_idx) };
if is_exclusive {
bucket.unlock_exclusive();
} else {
bucket.unlock_shared();
}
}
retry_count += 1;
if retry_count >= Self::YIELD_RETRY_BUDGET + Self::SLEEP_RETRY_BUDGET {
return Err(Error::LockTimeout);
}
if retry_count < Self::SPIN_RETRY_THRESHOLD {
let spin_limit = (1usize << retry_count.min(Self::SPIN_LIMIT_MAX_EXP))
| (retry_count & Self::SPIN_LIMIT_JITTER_MASK);
for _ in 0..spin_limit {
spin_loop();
}
} else if retry_count < Self::YIELD_RETRY_BUDGET {
yield_now();
} else {
let elapsed = retry_count - Self::YIELD_RETRY_BUDGET;
let backoff_us = Self::SLEEP_BASE_MICROS
.saturating_add(((elapsed >> 3) as u64).min(Self::SLEEP_MAX_ADDITIONAL_MICROS));
sleep(Duration::from_micros(backoff_us));
}
}
}
}
pub struct MultiBucketGuard<'a> {
index: &'a HashIndex,
stack: [(usize, bool); HashIndex::INLINE_LOCK_ENTRIES],
len: usize,
extra: Vec<(usize, bool)>,
}
impl<'a> MultiBucketGuard<'a> {
pub const INLINE_CAPACITY: usize = HashIndex::INLINE_LOCK_ENTRIES;
#[inline]
pub fn new(index: &'a HashIndex) -> Self {
Self {
index,
stack: [(0, false); Self::INLINE_CAPACITY],
len: 0,
extra: Vec::new(),
}
}
#[inline]
pub(crate) fn from_slice(index: &'a HashIndex, entries: &[(usize, bool)]) -> Self {
let count = entries.len();
if count <= Self::INLINE_CAPACITY {
let mut stack = [(0, false); Self::INLINE_CAPACITY];
stack[..count].copy_from_slice(entries);
Self {
index,
stack,
len: count,
extra: Vec::new(),
}
} else {
let mut stack = [(0, false); Self::INLINE_CAPACITY];
stack.copy_from_slice(&entries[..Self::INLINE_CAPACITY]);
Self {
index,
stack,
len: Self::INLINE_CAPACITY,
extra: entries[Self::INLINE_CAPACITY..].to_vec(),
}
}
}
#[inline]
pub fn len(&self) -> usize {
self.len + self.extra.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn iter(&self) -> impl DoubleEndedIterator<Item = &(usize, bool)> {
self.stack[..self.len].iter().chain(self.extra.iter())
}
}
impl Drop for MultiBucketGuard<'_> {
fn drop(&mut self) {
for &(bucket_idx, is_exclusive) in self.iter().rev() {
let bucket = unsafe { self.index.buckets.get_unchecked(bucket_idx) };
if is_exclusive {
bucket.unlock_exclusive();
} else {
bucket.unlock_shared();
}
}
}
}
#[inline(always)]
pub fn prefetch_read_l1<T>(p: *const T) {
#[cfg(target_arch = "x86_64")]
unsafe {
_mm_prefetch(p.cast(), _MM_HINT_T0);
}
#[cfg(target_arch = "aarch64")]
unsafe {
asm!(
"prfm pldl1keep, [{p}]",
p = in(reg) p,
options(nostack, readonly, preserves_flags)
);
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
{
let _ = p;
}
}