267 lines
8.7 KiB
Rust
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);
|
|
}
|