How to create backoff that is faster than pure yield while preserving CPU efficiency?

⚓ Rust    📅 2026-07-15    👤 surdeus    👁️ 2      

surdeus

I create backoff like this :

pub fn get_sender_auto(&self) -> &mut [MaybeUninit<T>] {
    if let Some(b) = self.get_sender() {
        return b;
    }

    loop {     
        let mut spin = 8;     

        for _ in 0..3 {
            for _ in 0..spin {
                if let Some(b) = self.get_sender() {
                    return b;
                }
                core::hint::spin_loop();
            }
            spin <<= 2;

            unsafe {
                libc::sched_yield();
            }   
        }

        println!("futex");

        self.producer.futex.store(FUTEX_WAITING);

        if let Some(b) = self.get_sender() {
            self.producer.futex.store(FUTEX_OPEN);
            return b;
        }

        self.producer.futex.wait(FUTEX_WAITING);

        if let Some(b) = self.get_sender() {
            return b;
        }     
    
    }
}

And then in the consumer, I wake the Futex in every call, aka :

// all processing to prepare the value

if self.producer.futex.load() == FUTEX_WAITING {
    if self.producer.futex.swap(FUTEX_OPEN) == FUTEX_WAITING {
        self.producer.futex.wake_one();
    }
}

// return the value

Now compared with the pure yield :

while sent < total_data {
    if let Some(s) = self.get_sender() {
        s.send(data);
        sent += total
    } else {
        std::thread::yield_now();
    }
}

The receiver is also pure yield :

loop {
    if let Some(val) = self.receive() {
        // using the value
    } else {
        std::thread::yield_now();
    }
}

The pure yield is always faster

── RUN 1 ──

── Thread Yield ──
source: 199999980000000, total: 199999980000000, match: true
Ring Bits        : 12
Ring Capacity    : 4096 elements
Total Producers  : 4
Total Messages   : 40000000 items
Execution Time   : 220.019619ms
Throughput       : 181.801.969 msg/sec

── Custom Backoff ──
source: 199999980000000, total: 199999980000000, match: true
Ring Bits        : 12
Ring Capacity    : 4096 elements
Total Producers  : 4
Total Messages   : 40000000 items
Execution Time   : 250.521769ms
Throughput       : 159.666.763 msg/sec

── RUN 2 ──

── Thread Yield ──
source: 199999980000000, total: 199999980000000, match: true
Ring Bits        : 12
Ring Capacity    : 4096 elements
Total Producers  : 4
Total Messages   : 40000000 items
Execution Time   : 199.973812ms
Throughput       : 200.026.191 msg/sec

── Custom Backoff ──
source: 199999980000000, total: 199999980000000, match: true
Ring Bits        : 12
Ring Capacity    : 4096 elements
Total Producers  : 4
Total Messages   : 40000000 items
Execution Time   : 253.88964ms
Throughput       : 157.548.768 msg/sec

── RUN 3 ──

── Thread Yield ──
source: 199999980000000, total: 199999980000000, match: true
Ring Bits        : 12
Ring Capacity    : 4096 elements
Total Producers  : 4
Total Messages   : 40000000 items
Execution Time   : 197.360402ms
Throughput       : 202.674.901 msg/sec

── Custom Backoff ──
source: 199999980000000, total: 199999980000000, match: true
Ring Bits        : 12
Ring Capacity    : 4096 elements
Total Producers  : 4
Total Messages   : 40000000 items
Execution Time   : 304.678084ms
Throughput       : 131.286.108 msg/sec

── RUN 4 ──

── Thread Yield ──
source: 199999980000000, total: 199999980000000, match: true
Ring Bits        : 12
Ring Capacity    : 4096 elements
Total Producers  : 4
Total Messages   : 40000000 items
Execution Time   : 215.068264ms
Throughput       : 185.987.459 msg/sec

── Custom Backoff ──
source: 199999980000000, total: 199999980000000, match: true
Ring Bits        : 12
Ring Capacity    : 4096 elements
Total Producers  : 4
Total Messages   : 40000000 items
Execution Time   : 317.148649ms
Throughput       : 126.123.822 msg/sec

── RUN 5 ──

── Thread Yield ──
source: 199999980000000, total: 199999980000000, match: true
Ring Bits        : 12
Ring Capacity    : 4096 elements
Total Producers  : 4
Total Messages   : 40000000 items
Execution Time   : 196.504987ms
Throughput       : 203.557.174 msg/sec

── Custom Backoff ──
source: 199999980000000, total: 199999980000000, match: true
Ring Bits        : 12
Ring Capacity    : 4096 elements
Total Producers  : 4
Total Messages   : 40000000 items
Execution Time   : 267.938161ms
Throughput       : 149.288.178 msg/sec

The pure yield is consistently has higher performance

The Futex in the producer is never be run because the println!("Futex") does not appear on console. So the problem is likely in the loop before Futex or the always call Futex wake up in the consumer. I'm not sure what is the exact cause, anyone know what is that causes the performance slower than pure yield?

Why is a spin loop started from 8 then exponential by 2 for 3 time with thread yield between the outer loop is slower than pure yield loop?

Does contantly call Futex wake up has overhead or it is no opt if the producer is not parked?

The machine used to test has 1 core CPU

2 posts - 2 participants

Read full topic

🏷️ Rust_feed