Skip to content

EricLBuehler/trc

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

86 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Trc

rustc 1.70.0 stable MIT License Build status Docs status Tests status

Trc is a performant heap-allocated smart pointer for Rust that implements a version of biased reference counting. Trc<T> stands for: Thread Reference Counted. Trc<T> provides a shared ownership of the data similar to Arc<T> and Rc<T>. It implements a custom version of biased reference counting, which is based on the observation that most objects are only used by one thread. This means that two reference counts can be created: one for thread-local use, and one atomic one for sharing between threads. This implementation of biased reference counting sets the atomic reference count to the number of threads using the data.

A cycle between Trc pointers cannot be deallocated as the reference counts will never reach zero. The solution is a Weak<T>. A Weak<T> is a non-owning reference to the data held by a Trc<T>. They break reference cycles by adding a layer of indirection and act as an observer. They cannot even access the data directly, and must be converted back into Trc<T>. Weak<T> does not keep the value alive (whcih can be dropped), and only keeps the backing allocation alive.

To soundly implement thread safety Trc<T> does not itself implement Send or Sync. However, SharedTrc<T> does, and it is the only way to safely send a Trc<T> across threads. See SharedTrc for it's API, which is similar to that of Weak.

Examples

Example of Trc<T> in a single thread:

use trc::Trc;

let mut trc = Trc::new(100);
assert_eq!(*trc, 100);
*trc = 200;
assert_eq!(*trc, 200);

Example of Trc<T> with multiple threads:

use std::thread;
use trc::Trc;
use trc::SharedTrc;

let trc = Trc::new(100);
let shared = SharedTrc::from_trc(&thread_trc_main);
let handle = thread::spawn(move || {
    let mut trc = SharedTrc::to_trc(shared);
    *trc2 = 200;
});

handle.join().unwrap();
assert_eq!(*trc, 200);

Example of Weak<T> in a single thread:

use trc::Trc;
use trc::Weak;

let trc = Trc::new(100);
let weak = Weak::from_trc(&trc);
let mut new_trc = Weak::to_trc(&weak).unwrap();
println!("Deref test! {}", *new_trc);
println!("DerefMut test");
*new_trc = 200;
println!("Deref test! {}", *new_trc);

Example of Weak<T> with multiple threads:

use std::thread;
use trc::Trc;
use trc::Weak;

let trc = Trc::new(100);
let weak = Weak::from_trc(&trc);

let handle = thread::spawn(move || {
    let mut trc = Weak::to_trc(&weak).unwrap();
    println!("{:?}", *trc);
    *trc = 200;
});
handle.join().unwrap();
println!("{}", *trc);
assert_eq!(*trc, 200);

Benchmarks

Benchmarks via Criterion. As can be seen, Trc's performance realy shines when there are many Clones. The reason Trc does not do as well for fewer operations is because it needs to allocate n+1 blocks of memory for n threads, and so for 1 thread, there are 2 allocations. However, after the initial allocations, Trc performs very well - 3.94x Arc's time for Clones.

Clone

Type Mean time
Trc 35.926ms
Arc 37.032ns
Rc 15.866ns

Multiple Clone (100 times)

Type Mean time
Trc 337.210ns
Arc 1327.000ns
Rc 293.71ns

Deref

Type Mean time
Trc 23.613ns
Arc 23.735ns
Rc 12.462ns

Multiple Deref (100 times)

Type Mean time
Trc 51.166ns
Arc 55.585ns
Rc 41.808ns

Use

To use Trc, simply run cargo add trc, or add trc = "1.1.18". Optionally, you can always use the latest version by adding trc = {git = "https://github.com/EricLBuehler/trc.git"}.