Files
core/test/rust/performance/extended/concurrency.rs
T
2026-11-03 03:32:07 +00:00

267 lines
8.7 KiB
Rust

use dever_runtime::async_stream::AsyncStream;
use dever_runtime::channel::Channel;
use dever_runtime::task::{Group, Limits, Runtime, Scope, Task};
struct ConcurrencyCase {
runtime: Runtime,
values: Vec<i64>,
limit: i64,
}
fn concurrency_fault() -> Fault {
&FaultInfo {
message: "extended concurrency failed",
source: "extended-concurrency-reference",
line: 1,
column: 1,
}
}
type ChannelOutput = (Vec<Option<i64>>, bool);
fn channel_generated(values: &[ConcurrencyCase]) -> Result<ChannelOutput, Fault> {
let Some(value) = values.first() else {
return Ok((Vec::new(), true));
};
value
.runtime
.run(async |scope| EXT_CHANNEL(&value.values, value.limit, scope).await)
.map_err(|_| concurrency_fault())?
.map_err(|_| concurrency_fault())
}
async fn send_values(values: Vec<i64>, channel: Channel<i64>) -> Result<(), Fault> {
for value in values {
channel.send(value).await.map_err(|_| concurrency_fault())?;
}
channel.close().await.map_err(|_| concurrency_fault())
}
fn channel_reference(values: &[ConcurrencyCase]) -> Result<ChannelOutput, Fault> {
let Some(value) = values.first() else {
return Ok((Vec::new(), true));
};
value
.runtime
.run(async |scope| {
let channel = Channel::new(value.limit).map_err(|_| concurrency_fault())?;
let producer = channel.clone();
let owned = value.values.clone();
let pending = scope
.spawn_leaf(move || send_values(owned, producer))
.map_err(|_| concurrency_fault())?;
let mut received = Vec::with_capacity(value.values.len());
for _ in &value.values {
received.push(channel.receive().await.map_err(|_| concurrency_fault())?);
}
pending.wait().await.map_err(|_| concurrency_fault())??;
let ended = channel
.receive()
.await
.map_err(|_| concurrency_fault())?
.is_none();
Ok((received, ended))
})
.map_err(|_| concurrency_fault())?
}
fn channel_score(value: &ChannelOutput) -> u64 {
value.0.iter().fold(u64::from(value.1), |score, item| {
score
.wrapping_mul(31)
.wrapping_add(item.unwrap_or(0) as u64)
})
}
async fn stream_total(stream: &AsyncStream<i64>, limit: Option<i64>) -> Result<i64, Fault> {
let _close = stream.close_on_drop();
let mut total = 0i64;
while let Some(value) = stream.next().await.map_err(|_| concurrency_fault())? {
total = total.checked_add(value).ok_or_else(concurrency_fault)?;
if limit.is_some_and(|limit| value >= limit) {
break;
}
}
Ok(total)
}
fn pull_generated(values: &[ConcurrencyCase]) -> Result<(i64, i64), Fault> {
let Some(value) = values.first() else {
return Ok((0, 0));
};
value
.runtime
.run_leaf(EXT_PULL(&value.values))
.map_err(|_| concurrency_fault())?
.map_err(|_| concurrency_fault())
}
fn prefix_generated(values: &[ConcurrencyCase]) -> Result<(i64, i64), Fault> {
let Some(value) = values.first() else {
return Ok((0, 0));
};
value
.runtime
.run_leaf(EXT_PREFIX(&value.values))
.map_err(|_| concurrency_fault())?
.map_err(|_| concurrency_fault())
}
fn stream_reference<const EARLY: bool>(values: &[ConcurrencyCase]) -> Result<(i64, i64), Fault> {
let Some(value) = values.first() else {
return Ok((0, 0));
};
value
.runtime
.run_leaf(async {
let stream = AsyncStream::values(value.values.clone());
let alias = stream.clone();
let total = stream_total(&stream, EARLY.then_some(8)).await?;
let after = stream_total(&alias, None).await?;
Ok((total, after))
})
.map_err(|_| concurrency_fault())?
}
fn stream_score(value: &(i64, i64)) -> u64 {
(value.0 as u64)
.wrapping_mul(31)
.wrapping_add(value.1 as u64)
}
async fn parallel_reference_inner(value: &ConcurrencyCase, scope: &Scope<'_>) -> Result<i64, Fault> {
let channel = Channel::new(1).map_err(|_| concurrency_fault())?;
let consumer = channel.clone();
let pending = scope
.spawn_leaf(
move || async move { stream_total(&AsyncStream::channel(consumer), None).await },
)
.map_err(|_| concurrency_fault())?;
let mut items = value.values.iter().copied();
let group: Group<Task<Result<(), Fault>>, Fault> =
Group::new(value.limit).map_err(|_| concurrency_fault())?;
group
.each(
|| std::future::ready(Ok(items.next())),
|item| {
let target = channel.clone();
scope.spawn_leaf(move || async move {
target.send(item).await.map_err(|_| concurrency_fault())
})
},
)
.await
.map_err(|_| concurrency_fault())??;
channel.close().await.map_err(|_| concurrency_fault())?;
pending.wait().await.map_err(|_| concurrency_fault())?
}
fn parallel_generated(values: &[ConcurrencyCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else {
return Ok(0);
};
value
.runtime
.run(async |scope| EXT_PARALLEL(&value.values, value.limit, scope).await)
.map_err(|_| concurrency_fault())?
.map_err(|_| concurrency_fault())
}
fn parallel_reference(values: &[ConcurrencyCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else {
return Ok(0);
};
value
.runtime
.run(async |scope| parallel_reference_inner(value, scope).await)
.map_err(|_| concurrency_fault())?
}
fn new_case(limit: i64) -> ConcurrencyCase {
ConcurrencyCase {
runtime: Runtime::new(Limits {
threads: 2,
workers: 2,
tasks: 8,
})
.unwrap(),
values: (1..=256).collect(),
limit,
}
}
fn main() {
// 两侧共用运行库与固定限额;计时含复制、提交、领取及作用域清理。
let mut case = new_case(1);
assert_eq!(
case.runtime.run_leaf(EXT_BOUNDARY()).unwrap().unwrap(),
(15, Some(9), 0)
);
let expected = case.values.iter().sum::<i64>();
println!("case,sample,implementation,elapsed_ns,checksum");
for capacity in [1, 64] {
case.limit = capacity;
let values = std::slice::from_ref(&case);
let received = case.values.iter().copied().map(Some).collect::<Vec<_>>();
assert_eq!(channel_generated(values).unwrap(), (received.clone(), true));
assert_eq!(channel_reference(values).unwrap(), (received, true));
compare_named(
&format!("channel_capacity_{capacity}_shared_runtime"),
values,
32,
channel_generated,
channel_reference,
channel_score,
"rust_runtime",
);
}
case.limit = 8;
let values = std::slice::from_ref(&case);
assert_eq!(pull_generated(values).unwrap(), (expected, 0));
assert_eq!(prefix_generated(values).unwrap(), (36, 0));
compare_named(
"async_stream_pull_shared_runtime",
values,
128,
pull_generated,
stream_reference::<false>,
stream_score,
"rust_runtime",
);
compare_named(
"async_stream_prefix_shared_runtime",
values,
128,
prefix_generated,
stream_reference::<true>,
stream_score,
"rust_runtime",
);
for limit in [1, 4] {
case.limit = limit;
let values = std::slice::from_ref(&case);
assert_eq!(parallel_generated(values).unwrap(), expected);
assert_eq!(parallel_reference(values).unwrap(), expected);
compare_named(
&format!("parallel_each_limit_{limit}_shared_runtime"),
values,
32,
parallel_generated,
parallel_reference,
|value| *value as u64,
"rust_runtime",
);
}
// 真正调用空输入 fixture,区别于 samples 的空用例切片。
case.values.clear();
let empty = std::slice::from_ref(&case);
assert_eq!(channel_generated(empty).unwrap(), (Vec::new(), true));
assert_eq!(channel_reference(empty).unwrap(), (Vec::new(), true));
assert_eq!(pull_generated(empty).unwrap(), (0, 0));
assert_eq!(prefix_generated(empty).unwrap(), (0, 0));
assert_eq!(stream_reference::<false>(empty).unwrap(), (0, 0));
assert_eq!(stream_reference::<true>(empty).unwrap(), (0, 0));
assert_eq!(parallel_generated(empty).unwrap(), 0);
assert_eq!(parallel_reference(empty).unwrap(), 0);
}