chore: use `jhash` to compute the `FutexKey` hash

This patch pays the price of making the instantiation of `FutexKey`
more expensive to achieve two goals:
* Minor: make `match_up` slightly faster
* Major: make futex bucket allocation balancing more robust
This commit is contained in:
Arthur Paulino 2025-08-15 10:51:16 -03:00 committed by Tate, Hongliang Tian
parent 91351e338f
commit c31c6110f6
3 changed files with 21 additions and 18 deletions

1
Cargo.lock generated
View File

@ -226,6 +226,7 @@ dependencies = [
"inherit-methods-macro",
"int-to-c-enum",
"intrusive-collections",
"jhash",
"keyable-arc",
"lending-iterator",
"libflate",

View File

@ -33,6 +33,7 @@ aster-bigtcp = { path = "libs/aster-bigtcp" }
atomic-integer-wrapper = { path = "libs/atomic-integer-wrapper" }
id-alloc = { path = "../ostd/libs/id-alloc" }
int-to-c-enum = { path = "libs/int-to-c-enum" }
jhash = { path = "libs/jhash" }
cpio-decoder = { path = "libs/cpio-decoder" }
xarray = { path = "libs/xarray" }
intrusive-collections = "0.9.5"

View File

@ -415,12 +415,9 @@ impl FutexBucketVec {
}
fn get_bucket(&self, key: &FutexKey) -> (usize, &SpinLock<FutexBucket>) {
let addr = key.addr();
// `addr` is a multiple of 4, so we can ignore the last 2 bits.
let relevant_addr = addr >> 2;
// Since `self.size()` is known to be a power of 2, the following is
// equivalent to `relevant_addr % self.size()`, buf faster.
let index = relevant_addr & (self.size() - 1);
// equivalent to `key.hash % self.size()`, buf faster.
let index = key.hash & (self.size() - 1);
(index, &self.vec[index])
}
@ -520,15 +517,14 @@ impl FutexItem {
/// The key of a futex used to mark a futex word.
#[derive(Debug, Clone)]
struct FutexKey {
addr: Vaddr,
/// A hash value deterministically computed from the `Vaddr` and `Option<Pid>`
/// associated with the futex on instantiation.
hash: usize,
bitset: FutexBitSet,
/// Specify whether this `FutexKey` is process private or shared. If `pid` is
/// None, then this `FutexKey` is shared.
pid: Option<Pid>,
}
impl FutexKey {
pub fn new(addr: Vaddr, bitset: FutexBitSet, pid: Option<Pid>) -> Result<Self> {
fn new(addr: Vaddr, bitset: FutexBitSet, pid: Option<Pid>) -> Result<Self> {
// "On all platforms, futexes are four-byte integers that must be aligned on a four-byte
// boundary."
// Reference: <https://man7.org/linux/man-pages/man2/futex.2.html>.
@ -539,16 +535,21 @@ impl FutexKey {
);
}
Ok(Self { addr, bitset, pid })
// Use `jhash` to hash `addr` and `pid`.
let hash = {
let addr_low = addr as u32;
let addr_high = (addr >> 32) as u32;
// Choose a different jhash seed (or salt) for each process (unless the futex is shared)
// to prevent common key patterns from causing excessive collisions in the table.
let seed = pid.unwrap_or(u32::MAX);
jhash::jhash_2vals(addr_low, addr_high, seed) as usize
};
Ok(Self { hash, bitset })
}
pub fn addr(&self) -> Vaddr {
self.addr
}
pub fn match_up(&self, another: &Self) -> bool {
// TODO: Use hash value to do match_up
self.addr == another.addr && (self.bitset & another.bitset) != 0 && self.pid == another.pid
fn match_up(&self, another: &Self) -> bool {
self.hash == another.hash && (self.bitset & another.bitset) != 0
}
}