Skip to content

Commit fd17302

Browse files
committed
Reduce size of Thread
Make insertion fully cold
1 parent ee60698 commit fd17302

3 files changed

Lines changed: 81 additions & 44 deletions

File tree

Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ rust-version = "1.59"
1313

1414
[features]
1515
# this feature provides performance improvements using nightly features
16-
nightly = []
16+
nightly = ["memoffset"]
1717

1818
[badges]
1919
travis-ci = { repository = "Amanieu/thread_local-rs" }
@@ -23,9 +23,10 @@ once_cell = "1.5.2"
2323
# this is required to gate `nightly` related code paths
2424
cfg-if = "1.0.0"
2525
crossbeam-utils = "0.8.15"
26+
memoffset = { version = "0.9.0", optional = true }
2627

2728
[dev-dependencies]
28-
criterion = "0.4.0"
29+
criterion = "0.4"
2930

3031
[[bench]]
3132
name = "thread_local"

src/lib.rs

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -188,10 +188,11 @@ impl<T: Send> ThreadLocal<T> {
188188
where
189189
F: FnOnce() -> T,
190190
{
191-
unsafe {
192-
self.get_or_try(|| Ok::<T, ()>(create()))
193-
.unchecked_unwrap_ok()
191+
if let Some(val) = self.get() {
192+
return val;
194193
}
194+
195+
self.insert(create)
195196
}
196197

197198
/// Returns the element for the current thread, or creates it if it doesn't
@@ -201,12 +202,11 @@ impl<T: Send> ThreadLocal<T> {
201202
where
202203
F: FnOnce() -> Result<T, E>,
203204
{
204-
let thread = thread_id::get();
205-
if let Some(val) = self.get_inner(thread) {
205+
if let Some(val) = self.get() {
206206
return Ok(val);
207207
}
208208

209-
Ok(self.insert(create()?))
209+
self.insert_maybe(create)
210210
}
211211

212212
fn get_inner(&self, thread: Thread) -> Option<&T> {
@@ -227,14 +227,22 @@ impl<T: Send> ThreadLocal<T> {
227227
}
228228

229229
#[cold]
230-
fn insert(&self, data: T) -> &T {
230+
fn insert_maybe<F: FnOnce() -> Result<T, E>, E>(&self, gen: F) -> Result<&T, E> {
231+
let data = gen()?;
232+
Ok(self.insert(|| data))
233+
}
234+
235+
#[cold]
236+
fn insert<F: FnOnce() -> T>(&self, gen: F) -> &T {
237+
// call the generator here, so it is #[cold] as well.
238+
let data = gen();
231239
let thread = thread_id::get();
232240
let bucket_atomic_ptr = unsafe { self.buckets.get_unchecked(thread.bucket) };
233241
let bucket_ptr: *const _ = bucket_atomic_ptr.load(Ordering::Acquire);
234242

235243
// If the bucket doesn't already exist, we need to allocate it
236244
let bucket_ptr = if bucket_ptr.is_null() {
237-
let new_bucket = allocate_bucket(thread.bucket_size);
245+
let new_bucket = allocate_bucket(thread.bucket_size());
238246

239247
match bucket_atomic_ptr.compare_exchange(
240248
ptr::null_mut(),
@@ -247,7 +255,7 @@ impl<T: Send> ThreadLocal<T> {
247255
// another thread stored a new bucket before we could,
248256
// and we can free our bucket and use that one instead
249257
Err(bucket_ptr) => {
250-
unsafe { deallocate_bucket(new_bucket, thread.bucket_size) }
258+
unsafe { deallocate_bucket(new_bucket, thread.bucket_size()) }
251259
bucket_ptr
252260
}
253261
}
@@ -496,9 +504,7 @@ impl<T: Send> Iterator for IntoIter<T> {
496504
fn next(&mut self) -> Option<T> {
497505
self.raw.next_mut(&mut self.thread_local).map(|entry| {
498506
*entry.present.get_mut() = false;
499-
unsafe {
500-
std::mem::replace(&mut *entry.value.get(), MaybeUninit::uninit()).assume_init()
501-
}
507+
unsafe { mem::replace(&mut *entry.value.get(), MaybeUninit::uninit()).assume_init() }
502508
})
503509
}
504510
fn size_hint(&self) -> (usize, Option<usize>) {

src/thread_id.rs

Lines changed: 60 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -49,38 +49,47 @@ static THREAD_ID_MANAGER: Lazy<Mutex<ThreadIdManager>> =
4949
/// A thread ID may be reused after a thread exits.
5050
#[derive(Clone, Copy)]
5151
pub(crate) struct Thread {
52-
/// The thread ID obtained from the thread ID manager.
53-
pub(crate) id: usize,
5452
/// The bucket this thread's local storage will be in.
5553
pub(crate) bucket: usize,
56-
/// The size of the bucket this thread's local storage will be in.
57-
pub(crate) bucket_size: usize,
5854
/// The index into the bucket this thread's local storage is in.
5955
pub(crate) index: usize,
6056
}
57+
6158
impl Thread {
59+
/// id: The thread ID obtained from the thread ID manager.
60+
#[inline]
6261
fn new(id: usize) -> Self {
6362
let bucket = usize::from(POINTER_WIDTH) - ((id + 1).leading_zeros() as usize) - 1;
6463
let bucket_size = 1 << bucket;
6564
let index = id - (bucket_size - 1);
65+
Self { bucket, index }
66+
}
6667

67-
Self {
68-
id,
69-
bucket,
70-
bucket_size,
71-
index,
72-
}
68+
/// The size of the bucket this thread's local storage will be in.
69+
#[inline]
70+
pub fn bucket_size(&self) -> usize {
71+
1 << self.bucket
7372
}
7473
}
7574

7675
cfg_if::cfg_if! {
7776
if #[cfg(feature = "nightly")] {
77+
use memoffset::offset_of;
78+
use std::ptr::null;
79+
use std::cell::UnsafeCell;
80+
7881
// This is split into 2 thread-local variables so that we can check whether the
7982
// thread is initialized without having to register a thread-local destructor.
8083
//
8184
// This makes the fast path smaller.
8285
#[thread_local]
83-
static mut THREAD: Option<Thread> = None;
86+
static THREAD: UnsafeCell<ThreadWrapper> = UnsafeCell::new(ThreadWrapper {
87+
self_ptr: null(),
88+
thread: Thread {
89+
index: 0,
90+
bucket: 0,
91+
},
92+
});
8493
thread_local! { static THREAD_GUARD: ThreadGuard = const { ThreadGuard { id: Cell::new(0) } }; }
8594

8695
// Guard to ensure the thread ID is released on thread exit.
@@ -97,17 +106,41 @@ cfg_if::cfg_if! {
97106
// will go through get_slow which will either panic or
98107
// initialize a new ThreadGuard.
99108
unsafe {
100-
THREAD = None;
109+
(&mut *THREAD.get()).self_ptr = null();
101110
}
102111
THREAD_ID_MANAGER.lock().free(self.id.get());
103112
}
104113
}
105114

115+
/// Data which is unique to the current thread while it is running.
116+
/// A thread ID may be reused after a thread exits.
117+
///
118+
/// This wrapper exists to hide multiple accesses to the TLS data
119+
/// from the backend as this can lead to inefficient codegen
120+
/// (to be precise it can lead to multiple TLS address lookups)
121+
#[derive(Clone, Copy)]
122+
struct ThreadWrapper {
123+
self_ptr: *const Thread,
124+
thread: Thread,
125+
}
126+
127+
impl ThreadWrapper {
128+
/// The thread ID obtained from the thread ID manager.
129+
#[inline]
130+
fn new(id: usize) -> Self {
131+
Self {
132+
self_ptr: ((THREAD.get().cast_const() as usize) + offset_of!(ThreadWrapper, thread)) as *const Thread,
133+
thread: Thread::new(id),
134+
}
135+
}
136+
}
137+
106138
/// Returns a thread ID for the current thread, allocating one if needed.
107139
#[inline]
108140
pub(crate) fn get() -> Thread {
109-
if let Some(thread) = unsafe { THREAD } {
110-
thread
141+
let thread = unsafe { *THREAD.get() };
142+
if !thread.self_ptr.is_null() {
143+
unsafe { thread.self_ptr.read() }
111144
} else {
112145
get_slow()
113146
}
@@ -116,12 +149,13 @@ cfg_if::cfg_if! {
116149
/// Out-of-line slow path for allocating a thread ID.
117150
#[cold]
118151
fn get_slow() -> Thread {
119-
let new = Thread::new(THREAD_ID_MANAGER.lock().alloc());
152+
let id = THREAD_ID_MANAGER.lock().alloc();
153+
let new = ThreadWrapper::new(id);
120154
unsafe {
121-
THREAD = Some(new);
155+
*THREAD.get() = new;
122156
}
123-
THREAD_GUARD.with(|guard| guard.id.set(new.id));
124-
new
157+
THREAD_GUARD.with(|guard| guard.id.set(id));
158+
new.thread
125159
}
126160
} else {
127161
// This is split into 2 thread-local variables so that we can check whether the
@@ -164,9 +198,10 @@ cfg_if::cfg_if! {
164198
/// Out-of-line slow path for allocating a thread ID.
165199
#[cold]
166200
fn get_slow(thread: &Cell<Option<Thread>>) -> Thread {
167-
let new = Thread::new(THREAD_ID_MANAGER.lock().alloc());
201+
let id = THREAD_ID_MANAGER.lock().alloc();
202+
let new = Thread::new(id);
168203
thread.set(Some(new));
169-
THREAD_GUARD.with(|guard| guard.id.set(new.id));
204+
THREAD_GUARD.with(|guard| guard.id.set(id));
170205
new
171206
}
172207
}
@@ -175,32 +210,27 @@ cfg_if::cfg_if! {
175210
#[test]
176211
fn test_thread() {
177212
let thread = Thread::new(0);
178-
assert_eq!(thread.id, 0);
179213
assert_eq!(thread.bucket, 0);
180-
assert_eq!(thread.bucket_size, 1);
214+
assert_eq!(thread.bucket_size(), 1);
181215
assert_eq!(thread.index, 0);
182216

183217
let thread = Thread::new(1);
184-
assert_eq!(thread.id, 1);
185218
assert_eq!(thread.bucket, 1);
186-
assert_eq!(thread.bucket_size, 2);
219+
assert_eq!(thread.bucket_size(), 2);
187220
assert_eq!(thread.index, 0);
188221

189222
let thread = Thread::new(2);
190-
assert_eq!(thread.id, 2);
191223
assert_eq!(thread.bucket, 1);
192-
assert_eq!(thread.bucket_size, 2);
224+
assert_eq!(thread.bucket_size(), 2);
193225
assert_eq!(thread.index, 1);
194226

195227
let thread = Thread::new(3);
196-
assert_eq!(thread.id, 3);
197228
assert_eq!(thread.bucket, 2);
198-
assert_eq!(thread.bucket_size, 4);
229+
assert_eq!(thread.bucket_size(), 4);
199230
assert_eq!(thread.index, 0);
200231

201232
let thread = Thread::new(19);
202-
assert_eq!(thread.id, 19);
203233
assert_eq!(thread.bucket, 4);
204-
assert_eq!(thread.bucket_size, 16);
234+
assert_eq!(thread.bucket_size(), 16);
205235
assert_eq!(thread.index, 4);
206236
}

0 commit comments

Comments
 (0)