-
Notifications
You must be signed in to change notification settings - Fork 469
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Future/Stream implementation for channel #264
Comments
In an attempt to benchmark a naiive conversion to #[macro_use]
extern crate criterion;
extern crate crossbeam_channel;
extern crate futures;
extern crate tokio;
use criterion::Criterion;
use crossbeam_channel::bounded;
use futures::future::lazy;
use futures::stream::Stream;
use futures::sync::mpsc::{channel, Receiver, Sender};
use futures::{Async, Poll};
use futures::{Future, Sink};
use std::sync::{Arc, Mutex};
use tokio::timer::Interval;
struct Handler {
total: Arc<Mutex<u32>>,
}
impl Handler {
pub fn add_to(&self, num: u32) {
let mut total = self.total.lock().unwrap();
*total += num;
}
}
#[derive(Clone)]
struct FutureReceiver<T> {
receiver: crossbeam_channel::Receiver<T>,
}
impl<T> Stream for FutureReceiver<T> {
type Item = T;
type Error = crossbeam_channel::TryRecvError;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
match self.receiver.try_recv() {
Ok(i) => Ok(Async::Ready(Some(i))),
Err(crossbeam_channel::TryRecvError::Empty) => Ok(Async::NotReady),
Err(e) => Err(e),
}
}
}
fn go_crossbeam() {
let handler = Arc::new(Handler {
total: Arc::new(Mutex::new(0)),
});
let handler_clone = handler.clone();
tokio::run(lazy(move || {
let (tx, rx) = bounded(1_024);
let future_rx = FutureReceiver { receiver: rx };
for _ in 0..100 {
let handler = handler_clone.clone();
let rx = future_rx.clone();
tokio::spawn(
rx.for_each(move |num| -> Result<(), crossbeam_channel::TryRecvError> {
handler.add_to(num);
Ok(())
})
.map_err(|_| ()),
);
}
let tx = Arc::new(tx);
tokio::spawn(
Interval::new(
std::time::Instant::now(),
std::time::Duration::from_micros(100),
)
.take(100)
.map(|_| 100)
.map_err(|e| {
eprintln!("Got interval error = {:?}", e);
})
.for_each(move |num| tx.send(num).map_err(|e| {
println!("send err = {:?}", e));
match e {
SendError
}
})
.map(|_| ()),
);
Ok(())
}));
} I'm getting a |
Yes, there's a prototype of futures-based channels that take inspiration from
That's a benchmark for the It should be efficient, but this naive implementation is not complete because we don't wake up sleeping receiver tasks when a message is sent into the channel.
The problem is that in |
@stjepang Thanks! This explains a lot not just about the issue at hand, but about how Tokio and Futures work in Rust! In the meantime, I would appreciate some quick advice: Do you think the multiple |
Yes, that sounds like a good idea! I'm curious: when sending an event message, do you pick one of the |
In this case, each event is actually a type that is typically encoded by 8 bytes (it's a CAN v1 message), so right now I'm envisioning However, as the messages are const across all time, I think a reference approach would work if they were bigger. |
So you're actually looking for a broadcast channel (like the |
Ah, yes, I am. Sorry, I forgot the terminologies were different and they meant different things. I hadn't seen the |
I believe we can close this. A future-based version of crossbeam-channel is on my todo list. |
@stjpang Has any progress been made for a high perf async channel? |
@najamelan There are other implementations, which you can already use, like the one included in tokio: https://github.com/tokio-rs/tokio/tree/master/tokio-sync (Though the one included in tokio-sync is 'just' mpsc not mpmc.) |
@cynecx I didn't know about tokio-sync. I'll check it out soon! Thanks for pointing me to it. |
A new crate just got published last night: https://crates.io/crates/channel-async |
That new crate is a nice, thin layer on top of |
@jnicholls It seems like channel-async just runs a loop polling |
@vulpyne Agreed. It would take a fundamental update to crossbeam-channel to have threads park and wait events signal in order to have the best support for futures. This is why this issue is here, and why that crate is an okay stop-gap until an efficient implementation can be done. |
@stjepang Where did this land on your priority list? If there is work underway but unfinished, making it available on a branch for someone else to take up the mantle would be awesome. |
There is now a channel implementation in the |
@stjepang I noticed that the implementation in async-std is not based on Crossbeam, is there any reason for this? Or was it simply not a good fit? |
@Thomasdezeeuw Actually, having looked at the code (async-rs/async-std#380), it looks like that the same algorithm is being used, however it doesn't really makes sense to reuse crossbeam here, since the notification model is simply different. |
@Thomasdezeeuw Rather than reimplementing literally the whole crate based on futures, I chose to do some things differently:
The channel in |
Hmm, can you explain why? Haven't encountered this connection before (and two minutes' thinking didn't reveal the answer).
(This sentence is a bit ambiguous; do you mean futures and tokio are not CSP-based, which is why they're the default there...? Or is there a missing word?) |
Perhaps my statement was a bit too strong, but it still stands, I think. Both Go and Anyways, I don't know how to explain this well right now and maybe should give it all some more thought. Sorry. :)
Sorry, that was a typo - I wanted to say unbounded channels are not the default :) So they have constructors called Go takes an even stronger stance and intentionally offers no unbounded channels for "philosophical" reasons. |
Sorry for being dense, I'm still not familiar enough with the details to infer the answer here. 😅 Is it something like: positive-capacity channels let you 'rollback' and push the elements from any extra channels back into their buffers in case more than one operation wanted to complete at the same time (vaguely similar to the logic in python-trio/trio#242 (comment))? (If that happens to be near the mark, then why is it harder for |
Because with threads pairing send-receive operations can really happen simultaneously, whereas this is not possible with tasks. With tasks, the sending task can "offer" a send to the receiving task, yield with What I'm getting at is, the sending and receiving side can't agree on whether an operation has been executed or was canceled with certainty. With threads, we can do that. |
Ohh. Interesting... thanks for explaining! |
I suppose I'm reopening an issue on the archived dedicated channels repo (otherwise I'd just make a comment).
@stjepang mentioned here that it would be easier to reimplement channels from scratch with
Future
support in mind ahead of time than to addFuture
support to the extant library.I have two questions:
Stream
naiively using a crossbeam channel, would it not be efficient? I see an implementation exists as a benchmark, at least for some usages of the channel.The primary reason I ask this for my own needs is that there appear to be no stable, working MPMC channels that also implement
Stream
. My application essentially wants a stream of events that occur at a rate of approximately 5 events per millisecond with a couple dozen listeners to that same stream of events. I've created a mini benchmark (not good; no need to share in my opinion but if anyone is curious I can make it public) for using a bunch offutures::sync::mpsc::channel
s, one for each listener, and publishing on each of thoseSender
s. It seems, given the benchmarks, that that might actually not be terribly inefficient (I can add 100 listeners then publish 10 messages per millisecond for 10 milliseconds in about 12 ms), but I'd rather do things the right way with aReceiver: Clone + Stream
, if I can.The text was updated successfully, but these errors were encountered: