atomic-backoff
Customizable backoff strategies for compare-and-swap loops and spin loops.
Compare-and-swap (CAS) loops and spin loops can often be optimized by adding backoff at each iteration, i.e. waiting a bit before the next iteration, in order to reduce the contention on the CPU's cache lines.
As the optimal backoff strategy depends on multiple factors, especially the expected
contention, this crate provides a generic BackoffStrategy trait to help customize algorithms
using CAS/spin loops. Typical backoff strategies like ExponentialBackoff are also provided.
Atomic types are extended with try_update_with_backoff/update_with_backoff methods,
mirroring their std try_update/update counterparts.
For handwritten CAS loops, see BackoffStrategy::backoff_reload and BackoffState;
for spin loops, see BackoffStrategy::backoff_until.
Example
use ;
use ;
let no_backoff = ;
let exponential = ;
println!;
// no backoff: 2.08ms, exponential backoff: 646µs
Retry strategies
Unlike most backoff implementations, a BackoffStrategy doesn't only decide how long to wait
after a failed CAS, but also what to do with the atomic value before retrying, through the
RetryStrategy variant it returns:
NoReload: retry with the value returned by the failed CAS;Reload: reload the atomic and retry with the up-to-date value;ReloadUntilUnchanged: reload the atomic and keep backing off while its value changes between reloads, then retry with the up-to-date value.
ReloadUntilUnchanged avoids attempting a CAS while the atomic is being actively modified, which
significantly reduces the number of failed CAS under contention, and the contention itself. However, it should only be
returned for a bounded number of iterations, as it could otherwise lead to starvation under sustained contention.
Comparison with crossbeam::utils::Backoff
crossbeam::utils::Backoff is strictly equivalent to ExponentialBackoff<6> when Backoff::spin is used, and ExponentialBackoff<10, 0, 7> when Backoff::snooze is used. However, it is not customizable, and especially it doesn't convey the way the atomic value should be reloaded.
On the other hand, ExponentialBackoff provides a UNTIL_UNCHANGED_LIMIT parameter to wait until the atomic stops being contended, which significantly impacts the overall contention.
Running the example above with ExponentialBackoff<6, N> for different values of N:
| Strategy | Time |
|---|---|
NoBackoff |
2.08ms |
ExponentialBackoff<6> (crossbeam's spin) |
1.15ms |
ExponentialBackoff<6, 4> |
646µs |
ExponentialBackoff<6, 8> |
569µs |
Features
std(default): enablesstd::thread::yield_nowinExponentialBackoff(YIELD_AFTERparameter). Without it, the crate isno_stdandExponentialBackoffkeeps spinning instead of yielding.portable-atomic: extendsportable-atomicatomic types to supporttry_update_with_backoff/update_with_backoff.
Loom support
loom atomic types are also extended to support try_update_with_backoff/update_with_backoff when compiled with
--cfg loom, so that algorithms built on this crate can be model-checked with loom without any
feature flag.
License
Licensed under either of
at your option.