Skip to content

Commit 3701bdc

Browse files
committed
Auto merge of #107329 - joboet:optimize_lazylock, r=m-ou-se
Optimize `LazyLock` size The initialization function was unnecessarily stored separately from the data to be initialized. Since both cannot exist at the same time, a `union` can be used, with the `Once` acting as discriminant. This unfortunately requires some extra methods on `Once` so that `Drop` can be implemented correctly and efficiently. `@rustbot` label +T-libs +A-atomic
2 parents 6d819a4 + 6520488 commit 3701bdc

File tree

6 files changed

+120
-17
lines changed

6 files changed

+120
-17
lines changed

Diff for: library/std/src/sync/lazy_lock.rs

+70-16
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,21 @@
1-
use crate::cell::Cell;
1+
use crate::cell::UnsafeCell;
22
use crate::fmt;
3+
use crate::mem::ManuallyDrop;
34
use crate::ops::Deref;
45
use crate::panic::{RefUnwindSafe, UnwindSafe};
5-
use crate::sync::OnceLock;
6+
use crate::sync::Once;
7+
8+
use super::once::ExclusiveState;
9+
10+
// We use the state of a Once as discriminant value. Upon creation, the state is
11+
// "incomplete" and `f` contains the initialization closure. In the first call to
12+
// `call_once`, `f` is taken and run. If it succeeds, `value` is set and the state
13+
// is changed to "complete". If it panics, the Once is poisoned, so none of the
14+
// two fields is initialized.
15+
union Data<T, F> {
16+
value: ManuallyDrop<T>,
17+
f: ManuallyDrop<F>,
18+
}
619

720
/// A value which is initialized on the first access.
821
///
@@ -43,16 +56,17 @@ use crate::sync::OnceLock;
4356
/// ```
4457
#[unstable(feature = "once_cell", issue = "74465")]
4558
pub struct LazyLock<T, F = fn() -> T> {
46-
cell: OnceLock<T>,
47-
init: Cell<Option<F>>,
59+
once: Once,
60+
data: UnsafeCell<Data<T, F>>,
4861
}
62+
4963
impl<T, F: FnOnce() -> T> LazyLock<T, F> {
5064
/// Creates a new lazy value with the given initializing
5165
/// function.
5266
#[inline]
5367
#[unstable(feature = "once_cell", issue = "74465")]
5468
pub const fn new(f: F) -> LazyLock<T, F> {
55-
LazyLock { cell: OnceLock::new(), init: Cell::new(Some(f)) }
69+
LazyLock { once: Once::new(), data: UnsafeCell::new(Data { f: ManuallyDrop::new(f) }) }
5670
}
5771

5872
/// Forces the evaluation of this lazy value and
@@ -74,10 +88,50 @@ impl<T, F: FnOnce() -> T> LazyLock<T, F> {
7488
#[inline]
7589
#[unstable(feature = "once_cell", issue = "74465")]
7690
pub fn force(this: &LazyLock<T, F>) -> &T {
77-
this.cell.get_or_init(|| match this.init.take() {
78-
Some(f) => f(),
79-
None => panic!("Lazy instance has previously been poisoned"),
80-
})
91+
this.once.call_once(|| {
92+
// SAFETY: `call_once` only runs this closure once, ever.
93+
let data = unsafe { &mut *this.data.get() };
94+
let f = unsafe { ManuallyDrop::take(&mut data.f) };
95+
let value = f();
96+
data.value = ManuallyDrop::new(value);
97+
});
98+
99+
// SAFETY:
100+
// There are four possible scenarios:
101+
// * the closure was called and initialized `value`.
102+
// * the closure was called and panicked, so this point is never reached.
103+
// * the closure was not called, but a previous call initialized `value`.
104+
// * the closure was not called because the Once is poisoned, so this point
105+
// is never reached.
106+
// So `value` has definitely been initialized and will not be modified again.
107+
unsafe { &*(*this.data.get()).value }
108+
}
109+
}
110+
111+
impl<T, F> LazyLock<T, F> {
112+
/// Get the inner value if it has already been initialized.
113+
fn get(&self) -> Option<&T> {
114+
if self.once.is_completed() {
115+
// SAFETY:
116+
// The closure has been run successfully, so `value` has been initialized
117+
// and will not be modified again.
118+
Some(unsafe { &*(*self.data.get()).value })
119+
} else {
120+
None
121+
}
122+
}
123+
}
124+
125+
#[unstable(feature = "once_cell", issue = "74465")]
126+
impl<T, F> Drop for LazyLock<T, F> {
127+
fn drop(&mut self) {
128+
match self.once.state() {
129+
ExclusiveState::Incomplete => unsafe { ManuallyDrop::drop(&mut self.data.get_mut().f) },
130+
ExclusiveState::Complete => unsafe {
131+
ManuallyDrop::drop(&mut self.data.get_mut().value)
132+
},
133+
ExclusiveState::Poisoned => {}
134+
}
81135
}
82136
}
83137

@@ -103,23 +157,23 @@ impl<T: Default> Default for LazyLock<T> {
103157
#[unstable(feature = "once_cell", issue = "74465")]
104158
impl<T: fmt::Debug, F> fmt::Debug for LazyLock<T, F> {
105159
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106-
f.debug_struct("Lazy").field("cell", &self.cell).finish_non_exhaustive()
160+
match self.get() {
161+
Some(v) => f.debug_tuple("LazyLock").field(v).finish(),
162+
None => f.write_str("LazyLock(Uninit)"),
163+
}
107164
}
108165
}
109166

110167
// We never create a `&F` from a `&LazyLock<T, F>` so it is fine
111168
// to not impl `Sync` for `F`
112-
// we do create a `&mut Option<F>` in `force`, but this is
113-
// properly synchronized, so it only happens once
114-
// so it also does not contribute to this impl.
115169
#[unstable(feature = "once_cell", issue = "74465")]
116-
unsafe impl<T, F: Send> Sync for LazyLock<T, F> where OnceLock<T>: Sync {}
170+
unsafe impl<T: Sync + Send, F: Send> Sync for LazyLock<T, F> {}
117171
// auto-derived `Send` impl is OK.
118172

119173
#[unstable(feature = "once_cell", issue = "74465")]
120-
impl<T, F: UnwindSafe> RefUnwindSafe for LazyLock<T, F> where OnceLock<T>: RefUnwindSafe {}
174+
impl<T: RefUnwindSafe + UnwindSafe, F: UnwindSafe> RefUnwindSafe for LazyLock<T, F> {}
121175
#[unstable(feature = "once_cell", issue = "74465")]
122-
impl<T, F: UnwindSafe> UnwindSafe for LazyLock<T, F> where OnceLock<T>: UnwindSafe {}
176+
impl<T: UnwindSafe, F: UnwindSafe> UnwindSafe for LazyLock<T, F> {}
123177

124178
#[cfg(test)]
125179
mod tests;

Diff for: library/std/src/sync/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ mod condvar;
186186
mod lazy_lock;
187187
mod mpmc;
188188
mod mutex;
189-
mod once;
189+
pub(crate) mod once;
190190
mod once_lock;
191191
mod poison;
192192
mod remutex;

Diff for: library/std/src/sync/once.rs

+16
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ pub struct OnceState {
4343
pub(crate) inner: sys::OnceState,
4444
}
4545

46+
pub(crate) enum ExclusiveState {
47+
Incomplete,
48+
Poisoned,
49+
Complete,
50+
}
51+
4652
/// Initialization value for static [`Once`] values.
4753
///
4854
/// # Examples
@@ -248,6 +254,16 @@ impl Once {
248254
pub fn is_completed(&self) -> bool {
249255
self.inner.is_completed()
250256
}
257+
258+
/// Returns the current state of the `Once` instance.
259+
///
260+
/// Since this takes a mutable reference, no initialization can currently
261+
/// be running, so the state must be either "incomplete", "poisoned" or
262+
/// "complete".
263+
#[inline]
264+
pub(crate) fn state(&mut self) -> ExclusiveState {
265+
self.inner.state()
266+
}
251267
}
252268

253269
#[stable(feature = "std_debug", since = "1.16.0")]

Diff for: library/std/src/sys/unsupported/once.rs

+11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::cell::Cell;
22
use crate::sync as public;
3+
use crate::sync::once::ExclusiveState;
34

45
pub struct Once {
56
state: Cell<State>,
@@ -44,6 +45,16 @@ impl Once {
4445
self.state.get() == State::Complete
4546
}
4647

48+
#[inline]
49+
pub(crate) fn state(&mut self) -> ExclusiveState {
50+
match self.state.get() {
51+
State::Incomplete => ExclusiveState::Incomplete,
52+
State::Poisoned => ExclusiveState::Poisoned,
53+
State::Complete => ExclusiveState::Complete,
54+
_ => unreachable!("invalid Once state"),
55+
}
56+
}
57+
4758
#[cold]
4859
#[track_caller]
4960
pub fn call(&self, ignore_poisoning: bool, f: &mut impl FnMut(&public::OnceState)) {

Diff for: library/std/src/sys_common/once/futex.rs

+11
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::sync::atomic::{
44
AtomicU32,
55
Ordering::{Acquire, Relaxed, Release},
66
};
7+
use crate::sync::once::ExclusiveState;
78
use crate::sys::futex::{futex_wait, futex_wake_all};
89

910
// On some platforms, the OS is very nice and handles the waiter queue for us.
@@ -78,6 +79,16 @@ impl Once {
7879
self.state.load(Acquire) == COMPLETE
7980
}
8081

82+
#[inline]
83+
pub(crate) fn state(&mut self) -> ExclusiveState {
84+
match *self.state.get_mut() {
85+
INCOMPLETE => ExclusiveState::Incomplete,
86+
POISONED => ExclusiveState::Poisoned,
87+
COMPLETE => ExclusiveState::Complete,
88+
_ => unreachable!("invalid Once state"),
89+
}
90+
}
91+
8192
// This uses FnMut to match the API of the generic implementation. As this
8293
// implementation is quite light-weight, it is generic over the closure and
8394
// so avoids the cost of dynamic dispatch.

Diff for: library/std/src/sys_common/once/queue.rs

+11
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ use crate::fmt;
6060
use crate::ptr;
6161
use crate::sync as public;
6262
use crate::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
63+
use crate::sync::once::ExclusiveState;
6364
use crate::thread::{self, Thread};
6465

6566
type Masked = ();
@@ -121,6 +122,16 @@ impl Once {
121122
self.state_and_queue.load(Ordering::Acquire).addr() == COMPLETE
122123
}
123124

125+
#[inline]
126+
pub(crate) fn state(&mut self) -> ExclusiveState {
127+
match self.state_and_queue.get_mut().addr() {
128+
INCOMPLETE => ExclusiveState::Incomplete,
129+
POISONED => ExclusiveState::Poisoned,
130+
COMPLETE => ExclusiveState::Complete,
131+
_ => unreachable!("invalid Once state"),
132+
}
133+
}
134+
124135
// This is a non-generic function to reduce the monomorphization cost of
125136
// using `call_once` (this isn't exactly a trivial or small implementation).
126137
//

0 commit comments

Comments
 (0)