Files
Notes/notes-service/vendor/futures-intrusive/benches/mpmc_channel.rs
T
2026-08-01 16:11:49 +03:00

372 lines
10 KiB
Rust

use criterion::{
criterion_group, criterion_main, Criterion, ParameterizedBenchmark,
};
use futures::{
executor::block_on, future::join_all, join, sink::SinkExt,
stream::StreamExt, FutureExt,
};
use futures_intrusive::channel::{
shared::channel, shared::unbuffered_channel, LocalChannel,
};
use std::time::Duration;
/// Elements to transfer per producer
const ELEMS_TO_SEND: usize = 1000;
/// Buffer size for buffered channels
const CHANNEL_BUFFER_SIZE: usize = 20;
/// Benchmark for Crossbeam channels
fn crossbeam_channel_variable_tx(producers: usize) {
let elems_per_producer = ELEMS_TO_SEND / producers;
let (tx, rx) = crossbeam::channel::bounded(CHANNEL_BUFFER_SIZE);
for _i in 0..producers {
let tx = tx.clone();
std::thread::spawn(move || {
for _i in 0..elems_per_producer {
tx.send(4).unwrap();
}
});
}
drop(tx);
loop {
let res = rx.recv();
if res.is_err() {
break;
}
}
}
/// variable producers, single consumer
fn futchan_bounded_variable_tx(producers: usize) {
use futures::channel::mpsc::channel;
let elems_per_producer = ELEMS_TO_SEND / producers;
let (tx, mut rx) = channel(CHANNEL_BUFFER_SIZE);
for _i in 0..producers {
let mut tx = tx.clone();
std::thread::spawn(move || {
block_on(async {
for _i in 0..elems_per_producer {
tx.send(4).await.unwrap();
}
});
});
}
drop(tx);
block_on(async {
loop {
let res = rx.next().await;
if res.is_none() {
break;
}
}
});
}
/// variable producers, single consumer
fn tokiochan_bounded_variable_tx(producers: usize) {
let elems_per_producer = ELEMS_TO_SEND / producers;
let (tx, mut rx) = tokio::sync::mpsc::channel(CHANNEL_BUFFER_SIZE);
for _i in 0..producers {
let tx = tx.clone();
std::thread::spawn(move || {
block_on(async {
for _i in 0..elems_per_producer {
tx.send(4).await.unwrap();
}
});
});
}
drop(tx);
block_on(async {
loop {
let res = rx.recv().await;
if res.is_none() {
break;
}
}
});
}
macro_rules! intrusive_channel_variable_tx {
($producers: expr, $channel_constructor: expr) => {
let elems_per_producer = ELEMS_TO_SEND / $producers;
let (tx, rx) = $channel_constructor;
for _i in 0..$producers {
let tx = tx.clone();
std::thread::spawn(move || {
block_on(async {
for _i in 0..elems_per_producer {
let r = tx.send(4).await;
assert!(r.is_ok());
}
});
});
}
drop(tx);
block_on(async {
loop {
let res = rx.receive().await;
if res.is_none() {
break;
}
}
});
};
}
/// variable producers, single consumer
fn intrusivechan_bounded_variable_tx(producers: usize) {
intrusive_channel_variable_tx!(
producers,
channel::<i32>(CHANNEL_BUFFER_SIZE)
);
}
/// variable producers, single consumer
fn intrusivechan_unbuffered_variable_tx(producers: usize) {
intrusive_channel_variable_tx!(producers, unbuffered_channel::<i32>());
}
/// variable producers, single consumer
fn futchan_bounded_variable_tx_single_thread(producers: usize) {
let elems_per_producer = ELEMS_TO_SEND / producers;
block_on(async {
let (tx, mut rx) = futures::channel::mpsc::channel(CHANNEL_BUFFER_SIZE);
let produce_done = join_all((0..producers).into_iter().map(|_| {
let mut tx = tx.clone();
async move {
for _i in 0..elems_per_producer {
tx.send(4).await.unwrap();
}
}
.boxed()
}));
drop(tx);
let consume_done = async {
loop {
let res = rx.next().await;
if res.is_none() {
break;
}
}
};
join!(produce_done, consume_done);
});
}
/// variable producers, single consumer
fn tokiochan_bounded_variable_tx_single_thread(producers: usize) {
let elems_per_producer = ELEMS_TO_SEND / producers;
block_on(async {
let (tx, mut rx) = tokio::sync::mpsc::channel(CHANNEL_BUFFER_SIZE);
let produce_done = join_all((0..producers).into_iter().map(|_| {
let tx = tx.clone();
async move {
for _i in 0..elems_per_producer {
tx.send(4).await.unwrap();
}
}
.boxed()
}));
drop(tx);
let consume_done = async {
loop {
let res = rx.recv().await;
if res.is_none() {
break;
}
}
};
join!(produce_done, consume_done);
});
}
macro_rules! intrusive_channel_variable_tx_single_thread {
($producers: expr, $channel_constructor: expr) => {
let elems_per_producer = ELEMS_TO_SEND / $producers;
block_on(async {
let (tx, rx) = $channel_constructor;
let produce_done =
join_all((0..$producers).into_iter().map(|_| {
let tx = tx.clone();
Box::pin(async move {
for _i in 0..elems_per_producer {
let r = tx.send(4).await;
assert!(r.is_ok());
}
})
}));
drop(tx);
let consume_done = async {
loop {
let res = rx.receive().await;
if res.is_none() {
break;
}
}
};
join!(produce_done, consume_done);
});
};
}
/// variable producers, single consumer
fn intrusivechan_bounded_variable_tx_single_thread(producers: usize) {
intrusive_channel_variable_tx_single_thread!(
producers,
channel::<i32>(CHANNEL_BUFFER_SIZE)
);
}
/// variable producers, single consumer
fn intrusivechan_unbuffered_variable_tx_single_thread(producers: usize) {
intrusive_channel_variable_tx_single_thread!(
producers,
unbuffered_channel::<i32>()
);
}
/// variable producers, single consumer
fn intrusive_local_chan_bounded_variable_tx_single_thread(producers: usize) {
let elems_per_producer = ELEMS_TO_SEND / producers;
block_on(async {
let rx = LocalChannel::<i32, [i32; CHANNEL_BUFFER_SIZE]>::new();
let produce_done = join_all((0..producers).into_iter().map(|_| {
Box::pin(async {
for _i in 0..elems_per_producer {
let r = rx.send(4).await;
assert!(r.is_ok());
}
})
}));
let consume_done = async {
let mut count = 0;
let needed = elems_per_producer * producers;
loop {
let _ = rx.receive().await.unwrap();
// The channel doesn't automatically get closed when producers are
// gone since producer and consumer are the same object type.
// Therefore we need to count receives.
count += 1;
if count == needed {
break;
}
}
};
join!(produce_done, consume_done);
});
}
fn criterion_benchmark(c: &mut Criterion) {
// Producer and consumer are running on the same thread
c.bench(
"Channels (Single Threaded)",
ParameterizedBenchmark::new(
"intrusive local channel with producers",
|b, &&producers| {
b.iter(|| {
intrusive_local_chan_bounded_variable_tx_single_thread(
producers,
)
})
},
&[5, 20, 100],
)
.with_function("intrusive channel with producers", |b, &&producers| {
b.iter(|| {
intrusivechan_bounded_variable_tx_single_thread(producers)
})
})
.with_function(
"intrusive unbuffered channel with producers",
|b, &&producers| {
b.iter(|| {
intrusivechan_unbuffered_variable_tx_single_thread(
producers,
)
})
},
)
.with_function(
"futures::channel::mpsc with producers",
|b, &&producers| {
b.iter(|| futchan_bounded_variable_tx_single_thread(producers))
},
)
.with_function(
"tokio::sync::mpsc with producers",
|b, &&producers| {
b.iter(|| {
tokiochan_bounded_variable_tx_single_thread(producers)
})
},
),
);
// Producer and consume run on a different thread
c.bench(
"Channels (Thread per producer)",
ParameterizedBenchmark::new(
"crossbeam channel with producers",
|b, &&producers| {
b.iter(|| crossbeam_channel_variable_tx(producers))
},
&[5, 20, 100],
)
.with_function("intrusive channel with producers", |b, &&producers| {
b.iter(|| intrusivechan_bounded_variable_tx(producers))
})
.with_function(
"intrusive unbuffered channel with producers",
|b, &&producers| {
b.iter(|| intrusivechan_unbuffered_variable_tx(producers))
},
)
.with_function(
"futures::channel::mpsc with producers",
|b, &&producers| b.iter(|| futchan_bounded_variable_tx(producers)),
)
.with_function(
"tokio::sync::mpsc with producers",
|b, &&producers| {
b.iter(|| tokiochan_bounded_variable_tx(producers))
},
),
);
}
criterion_group! {
name = benches;
config = Criterion::default().measurement_time(Duration::from_secs(10)).nresamples(50);
targets = criterion_benchmark
}
criterion_main!(benches);