Skip to content

Commit 7ce5ce8

Browse files
authored
perf: Pack the state and future of unfolds in the same memory (#2283)
* Pack the state and future of unfolds in the same memory * Use the same type for both sink and stream unfolds
1 parent 5559680 commit 7ce5ce8

File tree

4 files changed

+123
-74
lines changed

4 files changed

+123
-74
lines changed

futures-util/src/lib.rs

+51-39
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,21 @@
44
#![cfg_attr(feature = "cfg-target-has-atomic", feature(cfg_target_has_atomic))]
55
#![cfg_attr(feature = "read-initializer", feature(read_initializer))]
66
#![cfg_attr(feature = "write-all-vectored", feature(io_slice_advance))]
7-
87
#![cfg_attr(not(feature = "std"), no_std)]
9-
#![warn(missing_docs, missing_debug_implementations, rust_2018_idioms, unreachable_pub)]
8+
#![warn(
9+
missing_docs,
10+
missing_debug_implementations,
11+
rust_2018_idioms,
12+
unreachable_pub
13+
)]
1014
// It cannot be included in the published code because this lints have false positives in the minimum required version.
1115
#![cfg_attr(test, warn(single_use_lifetimes))]
1216
#![warn(clippy::all)]
13-
1417
// mem::take requires Rust 1.40, matches! requires Rust 1.42
1518
// Can be removed if the minimum supported version increased or if https://github.com/rust-lang/rust-clippy/issues/3941
1619
// get's implemented.
1720
#![allow(clippy::mem_replace_with_default, clippy::match_like_matches_macro)]
18-
1921
#![doc(test(attr(deny(warnings), allow(dead_code, unused_assignments, unused_variables))))]
20-
2122
#![cfg_attr(docsrs, feature(doc_cfg))]
2223

2324
#[cfg(all(feature = "cfg-target-has-atomic", not(feature = "unstable")))]
@@ -49,7 +50,7 @@ pub use self::async_await::*;
4950
pub mod __private {
5051
pub use crate::*;
5152
pub use core::{
52-
option::Option::{self, Some, None},
53+
option::Option::{self, None, Some},
5354
pin::Pin,
5455
result::Result::{Err, Ok},
5556
};
@@ -76,10 +77,7 @@ macro_rules! delegate_sink {
7677
self.project().$field.poll_ready(cx)
7778
}
7879

79-
fn start_send(
80-
self: core::pin::Pin<&mut Self>,
81-
item: $item,
82-
) -> Result<(), Self::Error> {
80+
fn start_send(self: core::pin::Pin<&mut Self>, item: $item) -> Result<(), Self::Error> {
8381
self.project().$field.start_send(item)
8482
}
8583

@@ -96,7 +94,7 @@ macro_rules! delegate_sink {
9694
) -> core::task::Poll<Result<(), Self::Error>> {
9795
self.project().$field.poll_close(cx)
9896
}
99-
}
97+
};
10098
}
10199

102100
macro_rules! delegate_future {
@@ -107,7 +105,7 @@ macro_rules! delegate_future {
107105
) -> core::task::Poll<Self::Output> {
108106
self.project().$field.poll(cx)
109107
}
110-
}
108+
};
111109
}
112110

113111
macro_rules! delegate_stream {
@@ -121,34 +119,40 @@ macro_rules! delegate_stream {
121119
fn size_hint(&self) -> (usize, Option<usize>) {
122120
self.$field.size_hint()
123121
}
124-
}
122+
};
125123
}
126124

127125
#[cfg(feature = "io")]
128126
#[cfg(feature = "std")]
129127
macro_rules! delegate_async_write {
130128
($field:ident) => {
131-
fn poll_write(self: core::pin::Pin<&mut Self>, cx: &mut core::task::Context<'_>, buf: &[u8])
132-
-> core::task::Poll<std::io::Result<usize>>
133-
{
129+
fn poll_write(
130+
self: core::pin::Pin<&mut Self>,
131+
cx: &mut core::task::Context<'_>,
132+
buf: &[u8],
133+
) -> core::task::Poll<std::io::Result<usize>> {
134134
self.project().$field.poll_write(cx, buf)
135135
}
136-
fn poll_write_vectored(self: core::pin::Pin<&mut Self>, cx: &mut core::task::Context<'_>, bufs: &[std::io::IoSlice<'_>])
137-
-> core::task::Poll<std::io::Result<usize>>
138-
{
136+
fn poll_write_vectored(
137+
self: core::pin::Pin<&mut Self>,
138+
cx: &mut core::task::Context<'_>,
139+
bufs: &[std::io::IoSlice<'_>],
140+
) -> core::task::Poll<std::io::Result<usize>> {
139141
self.project().$field.poll_write_vectored(cx, bufs)
140142
}
141-
fn poll_flush(self: core::pin::Pin<&mut Self>, cx: &mut core::task::Context<'_>)
142-
-> core::task::Poll<std::io::Result<()>>
143-
{
143+
fn poll_flush(
144+
self: core::pin::Pin<&mut Self>,
145+
cx: &mut core::task::Context<'_>,
146+
) -> core::task::Poll<std::io::Result<()>> {
144147
self.project().$field.poll_flush(cx)
145148
}
146-
fn poll_close(self: core::pin::Pin<&mut Self>, cx: &mut core::task::Context<'_>)
147-
-> core::task::Poll<std::io::Result<()>>
148-
{
149+
fn poll_close(
150+
self: core::pin::Pin<&mut Self>,
151+
cx: &mut core::task::Context<'_>,
152+
) -> core::task::Poll<std::io::Result<()>> {
149153
self.project().$field.poll_close(cx)
150154
}
151-
}
155+
};
152156
}
153157

154158
#[cfg(feature = "io")]
@@ -160,18 +164,22 @@ macro_rules! delegate_async_read {
160164
self.$field.initializer()
161165
}
162166

163-
fn poll_read(self: core::pin::Pin<&mut Self>, cx: &mut core::task::Context<'_>, buf: &mut [u8])
164-
-> core::task::Poll<std::io::Result<usize>>
165-
{
167+
fn poll_read(
168+
self: core::pin::Pin<&mut Self>,
169+
cx: &mut core::task::Context<'_>,
170+
buf: &mut [u8],
171+
) -> core::task::Poll<std::io::Result<usize>> {
166172
self.project().$field.poll_read(cx, buf)
167173
}
168174

169-
fn poll_read_vectored(self: core::pin::Pin<&mut Self>, cx: &mut core::task::Context<'_>, bufs: &mut [std::io::IoSliceMut<'_>])
170-
-> core::task::Poll<std::io::Result<usize>>
171-
{
175+
fn poll_read_vectored(
176+
self: core::pin::Pin<&mut Self>,
177+
cx: &mut core::task::Context<'_>,
178+
bufs: &mut [std::io::IoSliceMut<'_>],
179+
) -> core::task::Poll<std::io::Result<usize>> {
172180
self.project().$field.poll_read_vectored(cx, bufs)
173181
}
174-
}
182+
};
175183
}
176184

177185
#[cfg(feature = "io")]
@@ -188,7 +196,7 @@ macro_rules! delegate_async_buf_read {
188196
fn consume(self: core::pin::Pin<&mut Self>, amt: usize) {
189197
self.project().$field.consume(amt)
190198
}
191-
}
199+
};
192200
}
193201

194202
macro_rules! delegate_access_inner {
@@ -304,16 +312,19 @@ macro_rules! delegate_all {
304312
}
305313

306314
pub mod future;
307-
#[doc(hidden)] pub use crate::future::{FutureExt, TryFutureExt};
315+
#[doc(hidden)]
316+
pub use crate::future::{FutureExt, TryFutureExt};
308317

309318
pub mod stream;
310-
#[doc(hidden)] pub use crate::stream::{StreamExt, TryStreamExt};
319+
#[doc(hidden)]
320+
pub use crate::stream::{StreamExt, TryStreamExt};
311321

312322
#[cfg(feature = "sink")]
313323
#[cfg_attr(docsrs, doc(cfg(feature = "sink")))]
314324
pub mod sink;
315325
#[cfg(feature = "sink")]
316-
#[doc(hidden)] pub use crate::sink::SinkExt;
326+
#[doc(hidden)]
327+
pub use crate::sink::SinkExt;
317328

318329
pub mod task;
319330

@@ -329,10 +340,11 @@ pub mod compat;
329340
pub mod io;
330341
#[cfg(feature = "io")]
331342
#[cfg(feature = "std")]
332-
#[doc(hidden)] pub use crate::io::{AsyncReadExt, AsyncWriteExt, AsyncSeekExt, AsyncBufReadExt};
343+
#[doc(hidden)]
344+
pub use crate::io::{AsyncBufReadExt, AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
333345

334346
mod fns;
335-
347+
mod unfold_state;
336348

337349
cfg_target_has_atomic! {
338350
#[cfg(feature = "alloc")]

futures-util/src/sink/unfold.rs

+12-13
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::unfold_state::UnfoldState;
12
use core::{future::Future, pin::Pin};
23
use futures_core::ready;
34
use futures_core::task::{Context, Poll};
@@ -9,10 +10,9 @@ pin_project! {
910
#[derive(Debug)]
1011
#[must_use = "sinks do nothing unless polled"]
1112
pub struct Unfold<T, F, R> {
12-
state: Option<T>,
1313
function: F,
1414
#[pin]
15-
future: Option<R>,
15+
state: UnfoldState<T, R>,
1616
}
1717
}
1818

@@ -37,9 +37,8 @@ pin_project! {
3737
/// ```
3838
pub fn unfold<T, F, R>(init: T, function: F) -> Unfold<T, F, R> {
3939
Unfold {
40-
state: Some(init),
4140
function,
42-
future: None,
41+
state: UnfoldState::Value(init),
4342
}
4443
}
4544

@@ -56,24 +55,24 @@ where
5655

5756
fn start_send(self: Pin<&mut Self>, item: Item) -> Result<(), Self::Error> {
5857
let mut this = self.project();
59-
debug_assert!(this.future.is_none());
60-
let future = (this.function)(this.state.take().unwrap(), item);
61-
this.future.set(Some(future));
58+
let future = match this.state.as_mut().take_value() {
59+
Some(value) => (this.function)(value, item),
60+
None => panic!("start_send called without poll_ready being called first"),
61+
};
62+
this.state.set(UnfoldState::Future(future));
6263
Ok(())
6364
}
6465

6566
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
6667
let mut this = self.project();
67-
Poll::Ready(if let Some(future) = this.future.as_mut().as_pin_mut() {
68-
let result = match ready!(future.poll(cx)) {
68+
Poll::Ready(if let Some(future) = this.state.as_mut().project_future() {
69+
match ready!(future.poll(cx)) {
6970
Ok(state) => {
70-
*this.state = Some(state);
71+
this.state.set(UnfoldState::Value(state));
7172
Ok(())
7273
}
7374
Err(err) => Err(err),
74-
};
75-
this.future.set(None);
76-
result
75+
}
7776
} else {
7877
Ok(())
7978
})

futures-util/src/stream/unfold.rs

+26-22
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::unfold_state::UnfoldState;
12
use core::fmt;
23
use core::pin::Pin;
34
use futures_core::future::Future;
@@ -46,13 +47,13 @@ use pin_project_lite::pin_project;
4647
/// # });
4748
/// ```
4849
pub fn unfold<T, F, Fut, Item>(init: T, f: F) -> Unfold<T, F, Fut>
49-
where F: FnMut(T) -> Fut,
50-
Fut: Future<Output = Option<(Item, T)>>,
50+
where
51+
F: FnMut(T) -> Fut,
52+
Fut: Future<Output = Option<(Item, T)>>,
5153
{
5254
Unfold {
5355
f,
54-
state: Some(init),
55-
fut: None,
56+
state: UnfoldState::Value(init),
5657
}
5758
}
5859

@@ -61,9 +62,8 @@ pin_project! {
6162
#[must_use = "streams do nothing unless polled"]
6263
pub struct Unfold<T, F, Fut> {
6364
f: F,
64-
state: Option<T>,
6565
#[pin]
66-
fut: Option<Fut>,
66+
state: UnfoldState<T, Fut>,
6767
}
6868
}
6969

@@ -75,44 +75,48 @@ where
7575
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
7676
f.debug_struct("Unfold")
7777
.field("state", &self.state)
78-
.field("fut", &self.fut)
7978
.finish()
8079
}
8180
}
8281

8382
impl<T, F, Fut, Item> FusedStream for Unfold<T, F, Fut>
84-
where F: FnMut(T) -> Fut,
85-
Fut: Future<Output = Option<(Item, T)>>,
83+
where
84+
F: FnMut(T) -> Fut,
85+
Fut: Future<Output = Option<(Item, T)>>,
8686
{
8787
fn is_terminated(&self) -> bool {
88-
self.state.is_none() && self.fut.is_none()
88+
if let UnfoldState::Empty = self.state {
89+
true
90+
} else {
91+
false
92+
}
8993
}
9094
}
9195

9296
impl<T, F, Fut, Item> Stream for Unfold<T, F, Fut>
93-
where F: FnMut(T) -> Fut,
94-
Fut: Future<Output = Option<(Item, T)>>,
97+
where
98+
F: FnMut(T) -> Fut,
99+
Fut: Future<Output = Option<(Item, T)>>,
95100
{
96101
type Item = Item;
97102

98-
fn poll_next(
99-
self: Pin<&mut Self>,
100-
cx: &mut Context<'_>,
101-
) -> Poll<Option<Self::Item>> {
103+
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
102104
let mut this = self.project();
103105

104-
if let Some(state) = this.state.take() {
105-
this.fut.set(Some((this.f)(state)));
106+
if let Some(state) = this.state.as_mut().take_value() {
107+
this.state.set(UnfoldState::Future((this.f)(state)));
106108
}
107109

108-
let step = ready!(this.fut.as_mut().as_pin_mut()
109-
.expect("Unfold must not be polled after it returned `Poll::Ready(None)`").poll(cx));
110-
this.fut.set(None);
110+
let step = match this.state.as_mut().project_future() {
111+
Some(fut) => ready!(fut.poll(cx)),
112+
None => panic!("Unfold must not be polled after it returned `Poll::Ready(None)`"),
113+
};
111114

112115
if let Some((item, next_state)) = step {
113-
*this.state = Some(next_state);
116+
this.state.set(UnfoldState::Value(next_state));
114117
Poll::Ready(Some(item))
115118
} else {
119+
this.state.set(UnfoldState::Empty);
116120
Poll::Ready(None)
117121
}
118122
}

futures-util/src/unfold_state.rs

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
use core::pin::Pin;
2+
3+
/// UnfoldState used for stream and sink unfolds
4+
#[derive(Debug)]
5+
pub(crate) enum UnfoldState<T, R> {
6+
Value(T),
7+
Future(/* #[pin] */ R),
8+
Empty,
9+
}
10+
11+
impl<T, R> UnfoldState<T, R> {
12+
pub(crate) fn project_future(self: Pin<&mut Self>) -> Option<Pin<&mut R>> {
13+
// SAFETY Normal pin projection on the `Future` variant
14+
unsafe {
15+
match self.get_unchecked_mut() {
16+
Self::Future(f) => Some(Pin::new_unchecked(f)),
17+
_ => None,
18+
}
19+
}
20+
}
21+
22+
pub(crate) fn take_value(self: Pin<&mut Self>) -> Option<T> {
23+
// SAFETY We only move out of the `Value` variant which is not pinned
24+
match *self {
25+
Self::Value(_) => unsafe {
26+
match core::mem::replace(self.get_unchecked_mut(), UnfoldState::Empty) {
27+
UnfoldState::Value(v) => Some(v),
28+
_ => core::hint::unreachable_unchecked(),
29+
}
30+
},
31+
_ => None,
32+
}
33+
}
34+
}

0 commit comments

Comments
 (0)