compat only.Expand description
compatible layer for V2.0 API
§Migration from v2.* to v3
If you want to migrate to v3 API, you may add the flavor type in MTx, MRx, Tx, Rx type, and change the channel initialization function accordingly.
If you have a large project that use v2 API, and want to migrate gradually,
only need to change original import from use crossfire::* to use crossfire::compat::*.
This module provides the CompatFlavor which erase the difference between List and Array,
but registry only use RegistryMulti for spsc and mpsc for compatibility.
§Compatible consideration
- In the legacy API, the sender/receiver types had erased the signature between bounded or unbounded channels
- The low level queue implement is for MPMC regardless of MPSC/SPSC model (which is exactly the same with V2.1)
- The module structure in
crossfire::compat::*, is exactly the same as v2.xcrossfire::*. crossfire::compat::*`
§Incompatible notes
- keeping Into<AsyncStream<T, F>> for
AsyncRxTrait<T>is not possible, due toAsyncRxTrait<T>is erased out Flavor parameter, so we addAsyncRxTrait::to_stream()which returnsPin<Box<dyn futures_core::stream::Stream<Item = T>>>.
§The reason of complete API refactor
I know we all hate the contagious nature of generic code, and reluctant to use trait object,
it’s common practice to use static dispatch like enum-dispatch. Originally crossfire only
have 2 channel variance (CompatFlavor), when adding more channel flavor for specific scenario,
other than common list and array, and specialized implement for spsc, mpsc, etc,
I notice that when the flavor enum grow from 2 types to 4+ types,
although the positive result can be observed on Arm, there was a regression in x86 async benchmark,
which offset the optimization effort.
It’s impossible to erased the type while keeping the performance goal having so much types.
From the aspect of compiler:
- In blocking context, the compiler can eliminate the unused branch according to the context, and keeping the function calls inline, unless you put multiple variant of enum together into a collection.
- In async context, the compiler is ignorance, since most of the async code is indirect calls.
We can see in generated asm from cargo-show-asm, even you initialize the channel with ArrayQueue, there’s still
SeqQueue match branch inside the
RecvFuture::poll(). What’s worse when we have 4 types variant in the flavor enum, the compiler think the internal queue ops function no longer worth to inline (because overall flatten code will be too big), and the match branch might fallen back to a big match table instead of simple comparison. This is the reason of performance regression.
From the aspect of CPU:
- I had tried a manual Vtable by putting method ptr inside AsyncTx/AsyncRx, which is ok on X86, but Arm will have -50% penalty. It looks like Arm is poor on loading / caching function ptr.
- Generic Arm CPU has overall poor performance (1/3 ~ 1/2) compared to mainstream x86_64, and bad at atomic CAS, a big match branch might be not so obvious than the positive effect from changing some CAS to direct load/store in the lockless algorithm.
From the aspect of API usage:
- There’re already nice native select mechanisms on async ecology, we don’t have to worry about the difference of receiver types, for flexibility.
- For blocking context, it might be more common scenario to select from the same type of channels for efficiency.
- The crossbeam implementation of select is decouple from channel types and message type, which means the API is possible for crossfire too.
Re-exports§
pub use crate::AsyncRxTrait;pub use crate::AsyncTxTrait;pub use crate::BlockingRxTrait;pub use crate::BlockingTxTrait;pub use crate::RecvError;pub use crate::RecvTimeoutError;pub use crate::SendError;pub use crate::SendTimeoutError;pub use crate::TryRecvError;pub use crate::TrySendError;
Modules§
Enums§
- Compat
Flavor - Compatible flavor that wraps the Array and list type