Skip to content

Commit

Permalink
Merge pull request #22 from Koxiaet/list
Browse files Browse the repository at this point in the history
  • Loading branch information
Amanieu committed Jan 7, 2021
2 parents f616be4 + ef33f17 commit 1ac832a
Show file tree
Hide file tree
Showing 7 changed files with 292 additions and 349 deletions.
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

0 comments on commit 1ac832a

Please sign in to comment.