Code Monkey home page Code Monkey logo

lifeguard's Introduction

lifeguard CI Build

Object Pool Manager

API Documentation

Examples

Pool issues owned values wrapped in smartpointers.

extern crate lifeguard;
use lifeguard::*;

fn main() {
    let pool : Pool<String> = pool().with(StartingSize(10)).build();
    {
        let string = pool.new_from("Hello, World!"); // Remove a value from the pool
        assert_eq!(9, pool.size());
    } // Values that have gone out of scope are automatically moved back into the pool.
    assert_eq!(10, pool.size());
}

Values taken from the pool can be dereferenced to access/mutate their contents.

extern crate lifeguard;
use lifeguard::*;

fn main() {
    let pool : Pool<String> = pool().with(StartingSize(10)).build();
    let mut string = pool.new_from("cat");
    string.push_str("s love eating mice"); //string.as_mut() also works
    assert_eq!("cats love eating mice", *string);
}

Values can be unwrapped, detaching them from the pool.

extern crate lifeguard;
use lifeguard::*;

fn main() {
    let pool : Pool<String> = pool().with(StartingSize(10)).build();
    {
        let string : String = pool.new().detach();
    } // The String goes out of scope and is dropped; it is not returned to the pool
    assert_eq!(9, pool.size());
}

Values can be manually entered into / returned to the pool.

extern crate lifeguard;
use lifeguard::*;

fn main() {
    let pool : Pool<String> = pool().with(StartingSize(10)).build();
    {
        let string : String = pool.detached(); // An unwrapped String, detached from the Pool
        assert_eq!(9, pool.size());
        let rstring : Recycled<String> = pool.attach(string); // The String is attached to the pool again
        assert_eq!(9, pool.size()); // but it is still checked out from the pool
    } // rstring goes out of scope and is added back to the pool
    assert_eq!(10, pool.size());
}

Pool's builder API can be used to customize the behavior of the pool.

extern crate lifeguard;
use lifeguard::*;

fn main() {
 let pool : Pool<String> = pool()
   // The pool will allocate 128 values for immediate use. More will be allocated on demand.
   .with(StartingSize(128))
   // The pool will only grow up to 4096 values. Further values will be dropped.
   .with(MaxSize(4096))
   // The pool will use this closure (or other object implementing Supply<T>) to allocate
   .with(Supplier(|| String::with_capacity(1024)))
   .build();
  // ...
}

Highly Unscientific Benchmarks

Benchmark source can be found here. Tests were run on an early 2015 MacBook Pro.

Each benchmark comes in three flavors:

  1. tests::*_standard: Uses the system allocator to create new values.
  2. tests::*_pooled_rc: Uses a Pool to create new values which hold Rc references to the Pool. These values can be freely passed to other scopes.
  3. tests::*_pooled: Uses a Pool to create new values which hold & locally-scoped references to the Pool. These values are the cheapest to create but are bound to the lifetime of the Pool.

Uninitialized Allocation

Compares the cost of allocating a new String (using String::with_capacity, as String::new does not allocate immediately) with the cost of retrieving a String from the pool.

tests::allocation_standard                          ... bench:   5,322,513 ns/iter (+/- 985,898)
tests::allocation_pooled_rc                         ... bench:     784,885 ns/iter (+/- 95,245)
tests::allocation_pooled                            ... bench:     565,864 ns/iter (+/- 66,036)

Initialized Allocation

Compares the cost of allocating a new String and initializing it to a given value (via &str::to_owned) with the cost of retrieving a String from the pool and initializing it to the same value.

tests::initialized_allocation_standard              ... bench:   5,329,948 ns/iter (+/- 547,725)
tests::initialized_allocation_pooled_rc             ... bench:   1,151,493 ns/iter (+/- 119,293)
tests::initialized_allocation_pooled                ... bench:     927,214 ns/iter (+/- 147,935)

Vec<Vec<String>> Allocation

Creates a two-dimensional vector of initialized Strings. All Vecs and Strings created are from a Pool where applicable. Adapted from this benchmark.

tests::vec_vec_str_standard                         ... bench:   1,353,906 ns/iter (+/- 142,094)
tests::vec_vec_str_pooled_rc                        ... bench:     298,087 ns/iter (+/- 168,703)
tests::vec_vec_str_pooled                           ... bench:     251,082 ns/iter (+/- 24,408)

Ideas and PRs welcome!

Inspired by frankmcsherry's recycler.

lifeguard's People

Contributors

c-nixon avatar joe1994 avatar marwes avatar ryman avatar zslayton avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

lifeguard's Issues

Optimize unreachable branches

Use something like this crate to help the compiler optimize away bits like:

   fn as_ref(&self) -> &T {
    match self.value.as_ref() {
      Some(v) => v,
      // This is unreachable but is currently preserved by LLVM
      None => panic!("Recycled<T> smartpointer missing its value.")
    }
  }

Allow for polling futures.

There may be other reasons to walk the values Vec so perhaps this should be generic. This is useful if the Pool contains sockets(TCP) where periodic pings need to be replied to, but I can think of other reasons.

Offer a builder API

Pools come with a few options:

  • starting size
  • maximum size
  • an initialization reference value

This has caused a variety of constructors to pop up:

  • with_size
  • with_size_and_max
  • with_size_and_max_from

This should be exposed via a builder for simplicity. Something like:

let string_pool: Pool<String> = Pool::new()
  .with(StartingSize(15))
  .with(MaximumSize(100))
  .with(InitializationReference(my_value))
  .build();

Add rustdoc

At present the only documentation are the examples in the README. Add rustdoc comments and host the generated docs somewhere.

Recycled values cannot contain references to other values from the same Pool

The Drop implementation for the RecycledInner type is responsible for returning the value being dropped to the Pool that originally issued it.

The insert_or_drop method performs two operations:

  1. If the CappedCollection has available capacity, the provided value will be reset() and then added to the Pool again.
  2. If it does not have capacity, the value will be drop()ed.

If the value contains other values from the same Pool (e.g. it's a nested container, like a Vec that indirectly contains other Vecs), the call to reset() in step 1 will cause the program to panic!(). This happens because the call to insert_or_drop uses a mutable borrow of the RefCell<CappedCollection<_>>. A nested call to insert_or_drop will also try to mutably borrow the RefCell<CappedCollection<_>>, causing RefCell's runtime validation logic to panic!().

This can be fixed by refactoring the Drop implementation to:

  • Immutably borrow the RefCell<CappedCollection<_>> to test its available capacity.
    • If there's no space available, drop the value.
    • If there is space available, call reset() on the value being returned to the Pool. (Notice that we're now resetting it without holding a mutable reference to the RefCell<CappedCollection<_>>.) Then mutably borrow the collection and add the value to it.

Research whether `detach` is worth it

The detach() method on Recycled values is the only way that the Option<T> it contains can be emptied prior to drop being called. drop currently branches to handle the empty case. If detach is removed (or re-designed), the branch can be removed. This would optimize drop, which happens for all Recycled values eventually, instead of detach, which only happens if requested.

Heads-up: UB due to misuse of mem::uninitialized will soon lead to panic

Here, this crate causes UB by "Producing an invalid value". Concretely, it produces a value of an arbitrary type T with mem::uninitialized(). In the near future the call to mem::uninitialized() will panic to avoid UB, and our crater run determined that this crate will be affected.

mem::uninitialized() is deprecated since Rust 1.39. The intended replacement is MaybeUninit, which tracks the possibility of uninitialized values at the type level to make sure the compiler does not make any false assumptions. I see you are using ManuallyDrop; likely the fix will involve replacing that by MaybeUninit.

Evaluate offering a Weak pointer type

Because RcRecycled values hold an Rc reference to the Pool, the only way to free the memory used by the Pool is to drop the Pool and all of the references it has passed out.

If the Pool has gone out of scope, it can no longer be used to retrieve value instances. This means there is no value in keeping it on the heap so Recycled values can be returned to it -- we may as well drop() those values where they are.

We should replace the current Rc pointer with a Weak pointer so the Pool's lifetime is not unnecessarily extended by outstanding references. This will add a small amount of overhead to the Drop and detach() implementations, but is worth it to avoid unexpected memory overhead.

add hashed pool

I love the library!

I've tossed around a similar idea for artifact, and actually implemented it but didn't find much performance benefit. Maybe you can improve on my idea...

My application used a fair amount of Arc data types, but many of them were constructed from the user. Because of this, items that could simply be a pointer to an existing Arc end up being allocated anyway.

I'm not sure if lifeguard could offer an API for this, but I figured it was worth mentioning. I think keeping lifeguard as-is (as a focused library) is probably preferred, but thought you might find the idea intriguing ๐Ÿ˜„

add Recycler to compliment Supplier

Supplier is used to construct new instances, however the user may also want to control the maximum size allowed when items go back into the pool to avoid OOM.

I recommend the name Recycler, although you can use whatever name you think is fitting.

use case:

extern crate lifeguard;
use lifeguard::*;

fn main() {
 let pool : Pool<String> = pool()
   // The pool will allocate 128 values for immediate use. More will be allocated on demand.
   .with(StartingSize(128))
   // The pool will only grow up to 4096 values. Further values will be dropped.
   .with(MaxSize(4096))
   // The pool will use this closure (or other object implementing Supply<T>) to allocate
   .with(Supplier(|| String::with_capacity(1024)))
   // The pool will use this closure when adding recycled items back to the pool
   .with(Recycler(|s| {
        s.clear()
        // nightly only
        if s.capacity() > 4096 {
            s.shrink_to(4096); 
        }
    })
   .build();
  // ...
}

Wrap pointer value in ManuallyDrop instead of Option

We can eliminate the overhead of constantly having to check whether the contained value has been detach()ed by:

  • Wrapping it in ManuallyDrop instead of Option
  • Modifying detach() to completely deconstruct the value without relying on Drop being called later.

As seen in #11, this will grant a modest speed increase without hurting ergonomics.

Trait implementations for `Recycled`

Currently, Recycled doesn't forward very many traits, even if the underlying type implements them. In particular, fairly standard traits such as Clone, PartialOrd or Hash are inaccessible through Recycled and RcRecycled. This makes the wrapper type much harder to work with, and is pretty easy to fix. The impls section of Rc provides a fairly good list of traits that it's good for wrapper types to proxy.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.