I'm running in a multi-threaded environment where 64 bit values are being added to a global counter (u128
) 25-50 times a second.
I wrote a little snippet:
#[derive(Debug)]
pub struct AtomicU128 {
lsv : AtomicU64,
msv : AtomicU64,
}
impl AtomicU128 {
fn fetch_add(&self, value : u64) -> u128 {
let mut previous = self.lsv.fetch_add(value, Ordering::SeqCst) as u128;
previous += if previous + value as u128 > u64::MAX.into() {
let x = self.msv.fetch_add(1, Ordering::SeqCst);
x as u128
} else { 0 };
previous
}
}
fn main() {
let a_u128 : AtomicU128 = AtomicU128 { lsv: AtomicU64::new(u64::MAX), msv: AtomicU64::new(0), };
a_u128.fetch_add(1);
println!("{:?}",a_u128);
}
which has a gaping hole in how it's handling the lsv
64bit overflow.
If I wrap the operation in a Mutex, should I bother with the AtomicU64
types?
Edit:
I put the following together, but it feels a little cumbersome:
#[derive(Debug)]
struct AtomicU128 {
inner: Mutex<u128>,
}
impl AtomicU128 {
fn new(value: u128) -> Self {
AtomicU128 {
inner: Mutex::new(value),
}
}
fn add(&self, value: u128) {
let mut inner = self.inner.lock().unwrap();
*inner += value;
}
fn get(&self) -> u128 {
*self.inner.lock().unwrap()
}
}
fn main() {
let v = AtomicU128::new(u64::MAX.into());
println!("Before {:?}",v.get());
v.add(1);
println!("After {:?}",v.get());
}
Is there a better way to safely add to u128
types together?
You can add 32-bit integers into a 96-bit count by splitting the count into overlapping 64-bit ranges:
Let L = count mod 264
Let M = count >> 32 mod 264
Now, when you add x
, you atomically add into L, set x = (x>>32) + carry
, and atomically add into M.
When you read, you can consider the lower-order word to be "authoritative". If it doesn't match the overlapping bits in the higher-order word, then the difference between the values will let you deduce the appropriate correction.
If you want to 64-bit addends into a 128-bit count, you can split those addends into upper and lower 32-bit halves.
The upper 32-bit halves can be added into a 96-bit counth
, and the lower 32-bit halves can be added into a 96-bit countl
.
The total count is then always just (counth<<32) + countl
. Note that observers may not see the two updates to countl
and counth
as atomic, but they will at least see the total count as monotonically increasing.