-
Notifications
You must be signed in to change notification settings - Fork 669
Zero allocation futures #437
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
Conversation
0399bf3 to
cf2fd8c
Compare
Does this also open up a way to implement single-threaded executors without the penalty of atomic operations? |
|
@Rufflewind This opens way to It depends on what your single threaded executor looks like. Executors commonly need to share values with inner mutability. This PR allows you to, for example, clone a reference to a Making a special version of |
|
Thanks for the PR! I feel though that having restrictions on the size of types is a little draconian, would it be possible to perhaps relax that? My thinking is that the only reason we require std today is b/c trait Unpark: Send {
fn unpark(&self);
fn clone(&self) -> *mut Unpark;
unsafe fn drop(&mut self);
}And that way the vtable is buried in @Rufflewind that will be a very difficult restriction to get rid of unfortunately, a |
|
@alexcrichton Agree the restriction sucks. We could auto-fallback to Let me see if I follow. In your suggestion |
|
I also need to make |
|
I personally feel that between this and #436 the latter (#436) has more potential in terms of being a route forward. In #436 the task system is generalized to only require one |
|
I think I will try to hide the Arc from the public API though (as mentioned in the PR) to future proof. |
|
Downsides:
I don't have concrete use cases for the upsides. |
|
Actually, I've realized this is a generalization of #436. Consider the following usage of the #436 API: // core is an Arc<Core>, idx is an u64
spawn.poll_future(&core, idx)Is equivalent to the following with the API in this PR: struct IndexedUnpark {
exec : Arc<Core>,
index : u64
}
impl Clone for IndexedUnpark { /* ref_inc behaviour goes here */ }
impl Drop for IndexedUnpark { /* ref_dec behaviour goes here */ }
impl Unpark for IndexedUnpark { /* unpark by index behaviour goes here */ }
// When polling a spawn
let unpark = UnparkHandle::new(IndexedUnpark { exec : core.clone(), index : idx });
spawn.poll_future(&unpark);We don't need the Hardcoding max type size in this PR may feel like a hack, but #436 also has its clunkiness with harcoded |
4512bf3 to
898e6db
Compare
Get rid of `Arc` in the `poll` methods, replacing with a `UnparkHandle`. Implement a custom trait object that can clone itself by limiting the maximum size of the object. Upon `park`, instead of giving `Arc` references we clone the contents of the `UnparkHandle`, this generalizes the behaviour of cloning the `Arc`.
Simplifies implementation and usage of `UnparkHandle`
898e6db to
61fb69c
Compare
…ot Sync. MAX_UNPARK_BYTES is now exposed and configurable at compile-time by setting the FUTURES_MAX_UNPARK_BYTES env var. UnparkHandle is marked non-Sync therefore Task is non-Sync.
61fb69c to
0f12f1d
Compare
|
I've made the maximum size of the unpark configurable through an env var (at compile-time) using standard |
This is more correct because we take the maximum between the MAX_UNPARK_BYTES set by the dependencies and the app.
|
Changed it to use cargo features instead of env var + |
|
I now realize this has potential to do unaligned loads. I think it's fixable though. |
| @@ -1,5 +1,5 @@ | |||
| use core::ptr; | |||
| use core::mem::{forget, size_of}; | |||
| use core::mem::{self, forget, size_of}; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does importing self do?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use core::mem::{self} is equivalent to use core::mem.
|
The max size default and configurations are arbitrary right now, but thanks to @camlorn we have data to make an informed decison. I did a simple analysis over this collection of type sizes from rustc and the ecosystem. Disregarding types smaller than 8 bytes, we get the following numbers:
So 64 looks like an appropriate default. Supporting 128 and 256 as features makes sense. Any larger than that is unlikely to ever be needed. We can always add more features if someone comes up with an use case for sizes larger than 256. |
|
I've changed things to hide I also moved the |
cf06946 to
8910b62
Compare
Hide UnparkHandle from the public API. Take &Unpark + Clone in poll methods of Spawn. Move the 'static bound to the Unpark trait. Also remove MaxUnparkBytes512 and MaxUnparkBytes1024, it's unlikely that someone will use those.
8910b62 to
ba1182c
Compare
|
I caught up with @carllerche on gitter today to get some feedback on this, which he kindly provided! I was unable to convince him that this API is nicer and he was unable to convince me that the alternative API is nicer. This API exposes no methods that are unsafe to implement while the alternative avoids unsafe in the internals. In practice the executors may need unsafe even with this API, we would have to try out and see if this can really reduce the overall amount of unsafe. Also in this API the user dosen't have to create a trait object which avoids a clone when using However, he gave me one argument that kills the approach in this PR. A goal that I was not aware of is to get the size of I had a lot of fun and learned a lot implementing and discussing this, thanks to everyone who gave feedback! |
This PR shows that it's possible to have zero (extra) allocation futures, by generalizing
ArctoUnpark + Clone + 'static.Unparkno longer requiresSyncand this opens way to a bare bones version of tasks that work onno_std.Gets rid of
Arcin thepollmethods, taking&Unpark + Cloneinstead.replacing with a. Since trait objects can't beUnparkHandleClonethis implements a custom trait object that isCloneby limiting the maximum size of the object. Uponpark, instead of givingArcreferences we clone the contents of theUnparkHandle, this generalizes the behaviour of cloning theArc.We need to figure out the intersection with the overhaul in #436.
I think this is compatible with the extension toI now think this generalizes #436, see below. Hopefully this is compatible with theUnparkporposed, but doing both things may be overkill since they are both targeted at reducing the number of allocations in the tokio/futures interface.UnparkContextstuff in #436 though I haven't really taken a look at that part.There is
unsafein the implementation of the trait object, I think it's correct and I got it to not crash any tests, but the more eyes on it the better!The downsides is that this is a (small) breaking change and there are subtle interactions around inner mutability. The limitation on the size of the type may also be annoying. But you can always wrap your struct in an
Arcand get exactly the same semantics we have today.Of course the real motivation of this PR is to surprise @alexcrichton as per #432.
Fixes #430. Fixes #432. Supercedes #433.