Skip to content
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

Use a list instead of a hash map #22

Merged
merged 9 commits into from Jan 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 3 additions & 3 deletions .travis.yml
Expand Up @@ -5,7 +5,7 @@ rust:
- nightly
- beta
- stable
- 1.27.2
- 1.28.0

before_script:
- |
Expand All @@ -15,12 +15,12 @@ before_script:
script:
- travis-cargo build
- travis-cargo test
- travis-cargo bench
# Criterion doesn't build on 1.28.0
- travis-cargo --skip 1.28.0 bench -- --features criterion
- travis-cargo doc -- --no-deps

after_success:
- travis-cargo --only nightly doc-upload

env:
global:
- TRAVIS_CARGO_NIGHTLY_FEATURE=""
Expand Down
10 changes: 10 additions & 0 deletions Cargo.toml
Expand Up @@ -14,3 +14,13 @@ travis-ci = { repository = "Amanieu/thread_local-rs" }

[dependencies]
lazy_static = "1.0"

# This is actually a dev-dependency, see https://github.com/rust-lang/cargo/issues/1596
criterion = { version = "0.3.3", optional = true }

[dev-dependencies]

[[bench]]
name = "thread_local"
required-features = ["criterion"]
harness = false
8 changes: 4 additions & 4 deletions README.md
Expand Up @@ -3,10 +3,10 @@ thread_local

[![Build Status](https://travis-ci.org/Amanieu/thread_local-rs.svg?branch=master)](https://travis-ci.org/Amanieu/thread_local-rs) [![Crates.io](https://img.shields.io/crates/v/thread_local.svg)](https://crates.io/crates/thread_local)

This library provides the `ThreadLocal` and `CachedThreadLocal` types which
allow a separate copy of an object to be used for each thread. This allows for
per-object thread-local storage, unlike the standard library's `thread_local!`
macro which only allows static thread-local storage.
This library provides the `ThreadLocal` type which allow a separate copy of an
object to be used for each thread. This allows for per-object thread-local
storage, unlike the standard library's `thread_local!` macro which only allows
static thread-local storage.

[Documentation](https://amanieu.github.io/thread_local-rs/thread_local/index.html)

Expand Down
36 changes: 21 additions & 15 deletions benches/thread_local.rs
@@ -1,22 +1,28 @@
#![feature(test)]

extern crate test;
extern crate criterion;
extern crate thread_local;

use thread_local::{CachedThreadLocal, ThreadLocal};
use criterion::{black_box, BatchSize};

use thread_local::ThreadLocal;

#[bench]
fn thread_local(b: &mut test::Bencher) {
let local = ThreadLocal::new();
b.iter(|| {
let _: &i32 = local.get_or(|| Box::new(0));
fn main() {
let mut c = criterion::Criterion::default().configure_from_args();

c.bench_function("get", |b| {
let local = ThreadLocal::new();
local.get_or(|| Box::new(0));
b.iter(|| {
black_box(local.get());
});
});
}

#[bench]
fn cached_thread_local(b: &mut test::Bencher) {
let local = CachedThreadLocal::new();
b.iter(|| {
let _: &i32 = local.get_or(|| Box::new(0));
c.bench_function("insert", |b| {
b.iter_batched_ref(
ThreadLocal::new,
|local| {
black_box(local.get_or(|| 0));
},
BatchSize::SmallInput,
)
});
}
103 changes: 33 additions & 70 deletions src/cached.rs
@@ -1,25 +1,19 @@
#![allow(deprecated)]

use super::{IntoIter, IterMut, ThreadLocal};
use std::cell::UnsafeCell;
use std::fmt;
use std::panic::UnwindSafe;
use std::sync::atomic::{AtomicUsize, Ordering};
use thread_id;
use unreachable::{UncheckedOptionExt, UncheckedResultExt};
use std::usize;

/// Wrapper around `ThreadLocal` which adds a fast path for a single thread.
/// Wrapper around [`ThreadLocal`].
///
/// This has the same API as `ThreadLocal`, but will register the first thread
/// that sets a value as its owner. All accesses by the owner will go through
/// a special fast path which is much faster than the normal `ThreadLocal` path.
/// This used to add a fast path for a single thread, however that has been
/// obsoleted by performance improvements to [`ThreadLocal`] itself.
#[deprecated(since = "1.1.0", note = "Use `ThreadLocal` instead")]
pub struct CachedThreadLocal<T: Send> {
owner: AtomicUsize,
local: UnsafeCell<Option<Box<T>>>,
global: ThreadLocal<T>,
inner: ThreadLocal<T>,
}

// CachedThreadLocal is always Sync, even if T isn't
unsafe impl<T: Send> Sync for CachedThreadLocal<T> {}

impl<T: Send> Default for CachedThreadLocal<T> {
fn default() -> CachedThreadLocal<T> {
CachedThreadLocal::new()
Expand All @@ -28,82 +22,49 @@ impl<T: Send> Default for CachedThreadLocal<T> {

impl<T: Send> CachedThreadLocal<T> {
/// Creates a new empty `CachedThreadLocal`.
#[inline]
pub fn new() -> CachedThreadLocal<T> {
CachedThreadLocal {
owner: AtomicUsize::new(0),
local: UnsafeCell::new(None),
global: ThreadLocal::new(),
inner: ThreadLocal::new(),
}
}

/// Returns the element for the current thread, if it exists.
#[inline]
pub fn get(&self) -> Option<&T> {
let id = thread_id::get();
let owner = self.owner.load(Ordering::Relaxed);
if owner == id {
return unsafe { Some((*self.local.get()).as_ref().unchecked_unwrap()) };
}
if owner == 0 {
return None;
}
self.global.get_fast(id)
self.inner.get()
}

/// Returns the element for the current thread, or creates it if it doesn't
/// exist.
#[inline(always)]
#[inline]
pub fn get_or<F>(&self, create: F) -> &T
where
F: FnOnce() -> T,
{
unsafe {
self.get_or_try(|| Ok::<T, ()>(create()))
.unchecked_unwrap_ok()
}
self.inner.get_or(create)
}

/// Returns the element for the current thread, or creates it if it doesn't
/// exist. If `create` fails, that error is returned and no element is
/// added.
#[inline]
pub fn get_or_try<F, E>(&self, create: F) -> Result<&T, E>
where
F: FnOnce() -> Result<T, E>,
{
let id = thread_id::get();
let owner = self.owner.load(Ordering::Relaxed);
if owner == id {
return Ok(unsafe { (*self.local.get()).as_ref().unchecked_unwrap() });
}
self.get_or_try_slow(id, owner, create)
}

#[cold]
#[inline(never)]
fn get_or_try_slow<F, E>(&self, id: usize, owner: usize, create: F) -> Result<&T, E>
where
F: FnOnce() -> Result<T, E>,
{
if owner == 0 && self.owner.compare_and_swap(0, id, Ordering::Relaxed) == 0 {
unsafe {
(*self.local.get()) = Some(Box::new(create()?));
return Ok((*self.local.get()).as_ref().unchecked_unwrap());
}
}
match self.global.get_fast(id) {
Some(x) => Ok(x),
None => Ok(self.global.insert(id, Box::new(create()?), true)),
}
self.inner.get_or_try(create)
}

/// Returns a mutable iterator over the local values of all threads.
///
/// Since this call borrows the `ThreadLocal` mutably, this operation can
/// be done safely---the mutable borrow statically guarantees no other
/// threads are currently accessing their associated values.
#[inline]
pub fn iter_mut(&mut self) -> CachedIterMut<T> {
CachedIterMut {
local: unsafe { (*self.local.get()).as_mut().map(|x| &mut **x) },
global: self.global.iter_mut(),
inner: self.inner.iter_mut(),
}
}

Expand All @@ -113,8 +74,9 @@ impl<T: Send> CachedThreadLocal<T> {
/// Since this call borrows the `ThreadLocal` mutably, this operation can
/// be done safely---the mutable borrow statically guarantees no other
/// threads are currently accessing their associated values.
#[inline]
pub fn clear(&mut self) {
*self = CachedThreadLocal::new();
self.inner.clear();
}
}

Expand All @@ -124,8 +86,7 @@ impl<T: Send> IntoIterator for CachedThreadLocal<T> {

fn into_iter(self) -> CachedIntoIter<T> {
CachedIntoIter {
local: unsafe { (*self.local.get()).take().map(|x| *x) },
global: self.global.into_iter(),
inner: self.inner.into_iter(),
}
}
}
Expand Down Expand Up @@ -156,42 +117,44 @@ impl<T: Send + fmt::Debug> fmt::Debug for CachedThreadLocal<T> {
impl<T: Send + UnwindSafe> UnwindSafe for CachedThreadLocal<T> {}

/// Mutable iterator over the contents of a `CachedThreadLocal`.
#[deprecated(since = "1.1.0", note = "Use `IterMut` instead")]
pub struct CachedIterMut<'a, T: Send + 'a> {
local: Option<&'a mut T>,
global: IterMut<'a, T>,
inner: IterMut<'a, T>,
}

impl<'a, T: Send + 'a> Iterator for CachedIterMut<'a, T> {
type Item = &'a mut T;

#[inline]
fn next(&mut self) -> Option<&'a mut T> {
self.local.take().or_else(|| self.global.next())
self.inner.next()
}

#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.global.size_hint().0 + self.local.is_some() as usize;
(len, Some(len))
self.inner.size_hint()
}
}

impl<'a, T: Send + 'a> ExactSizeIterator for CachedIterMut<'a, T> {}

/// An iterator that moves out of a `CachedThreadLocal`.
#[deprecated(since = "1.1.0", note = "Use `IntoIter` instead")]
pub struct CachedIntoIter<T: Send> {
local: Option<T>,
global: IntoIter<T>,
inner: IntoIter<T>,
}

impl<T: Send> Iterator for CachedIntoIter<T> {
type Item = T;

#[inline]
fn next(&mut self) -> Option<T> {
self.local.take().or_else(|| self.global.next())
self.inner.next()
}

#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.global.size_hint().0 + self.local.is_some() as usize;
(len, Some(len))
self.inner.size_hint()
}
}

Expand Down