Avoid double indirection in nested smart pointers.

Overview

Pierce

crates.io crates.io

Avoid double indirection in nested smart pointers.

The Pierce stuct allows you to cache the deref result of doubly-nested smart pointers.

Quick Example

use std::sync::Arc;
use pierce::Pierce;
let vec: Vec<i32> = vec![1, 2, 3];
let arc_vec = Arc::new(vec);
let pierce = Pierce::new(arc_vec);

// Here, the execution jumps directly to the slice to call `.get(...)`.
// Without Pierce it would have to jump to the Vec first,
// than from the Vec to the slice.
pierce.get(0).unwrap();

Nested Smart Pointers

Smart Pointers can be nested to - in a way - combine their functionalities. For example, with Arc<Vec<i32>>, a slice of i32 is managed by the wrapping Vec that is wrapped again by the Arc.

However, nesting comes at the cost of double indirection: when we want to access the underlying data, we must first follow the outer pointer to where the inner pointer lies, then follow the inner pointer to where the underlying data is. Two deref-ings. Two jumps.

use std::sync::Arc;
let vec: Vec<i32> = vec![1, 2, 3];
let arc_vec = Arc::new(vec);

// Here, the `Arc<Vec<i32>>` is first dereferenced to the `Vec<i32>`,
// then the Vec is dereferenced to the underlying i32 slice,
// on which `.get(...)` is called.
arc_vec.get(0).unwrap();

Pierce

The Pierce struct, provided by this crate, can help reduce the performance cost of nesting smart pointers by caching the deref result. We double-deref the nested smart pointer at the start, storing the address where the inner pointer points to. We can then access the underlying data by just jumping to the stored address. One jump.

Here's a diagram of what it might look like.

             ┌───────────────────────────┬───────────────────────────────┬──────────────────────────────────────────┐
             │ Stack                     │ Heap                          │ Heap                                     │
┌────────────┼───────────────────────────┼───────────────────────────────┼──────────────────────────────────────────┤
│ T          │                           │                               │                                          │
│            │  ┌──────────────────┐     │     ┌───────────────────┐     │    ┌──────────────────────────────────┐  │
│            │  │Outer Pointer     │     │     │Inner Pointer      │     │    │Target                            │  │
│            │  │                  │     │     │                   │     │    │                                  │  │
│            │  │        T ────────────────────────► T::Target ─────────────────► <T::Target as Deref>::Target   │  │
│            │  │                  │     │     │                   │     │    │                                  │  │
│            │  └──────────────────┘     │     └───────────────────┘     │    └──────────────────────────────────┘  │
│            │                           │                               │                                          │
├────────────┼───────────────────────────┼───────────────────────────────┼──────────────────────────────────────────┤
│ Pierce<T>  │                           │                               │                                          │
│            │  ┌──────────────────┐     │     ┌───────────────────┐     │    ┌──────────────────────────────────┐  │
│            │  │Outer Pointer     │     │     │Inner Pointer      │     │    │Target                            │  │
│            │  │                  │     │     │                   │     │    │                                  │  │
│            │  │        T ────────────────────────► T::Target ─────────────────► <T::Target as Deref>::Target   │  │
│            │  │                  │     │     │                   │     │    │                ▲                 │  │
│            │  ├──────────────────┤     │     └───────────────────┘     │    └────────────────│─────────────────┘  │
│            │  │Cache             │     │                               │                     │                    │
│            │  │                  │     │                               │                     │                    │
│            │  │       ptr ───────────────────────────────────────────────────────────────────┘                    │
│            │  │                  │     │                               │                                          │
│            │  └──────────────────┘     │                               │                                          │
│            │                           │                               │                                          │
└────────────┴───────────────────────────┴───────────────────────────────┴──────────────────────────────────────────┘

Usage

Pierce<T> can be created with Pierce::new(...). T should be a doubly-nested pointer (e.g. Arc<Vec<_>>, Box<Box<_>>).

deref-ing a Pierce<T> returns &<T::Target as Deref>::Target, i.e. the deref target of the deref target of T (the outer pointer that is wrapped by Pierce), i.e. the deref target of the inner pointer.

You can also obtain a borrow of just T (the outer pointer) using .borrow_inner().

See the docs at Pierce for more details.

Deeper Nesting

A Pierce reduces two jumps to one. If you have deeper nestings, you can wrap it multiple times.

use pierce::Pierce;
let triply_nested: Box<Box<Box<i32>>> = Box::new(Box::new(Box::new(42)));
assert_eq!(***triply_nested, 42); // <- Three jumps!
let pierce_twice = Pierce::new(Pierce::new(triply_nested));
assert_eq!(*pierce_twice, 42); // <- Just one jump!

Benchmarks

These benchmarks probably won't represent your use case at all because:

  • They are engineered to make Pierce look good.
  • Compiler optimizations are hard to control.
  • CPU caches and predictions are hard to control. (I bet the figures will be very different on your CPU.)
  • Countless other reasons why you shouldn't trust synthetic benchmarks.

Do your own benchmarks on real-world uses.

That said, here are my results:

Benchmark 1: Read items from a Box<Vec<usize>>, with simulated memory fragmentation.

Benchmark 2: Read items from a SlowBox<Vec<usize>>. SlowBox deliberately slow down deref() call greatly.

Benchmark 3: Read several Box<Box<i64>>.

Time taken by Pierce<T> version compared to T version.

Run Benchmark 1 Benchmark 2 Benchmark 3
1 -40.23% -99.69% -5.68%
2 -40.59% -99.69% -5.16%
3 -40.70% -99.68% +2.69%
4 -39.85% -99.68% -5.35%
5 -38.90% -99.71% -5.02%
6 -39.12% -99.69% -5.53%
7 -40.51% -99.69% -6.09%
8 -26.99% -99.71% -6.43%

See the benchmarks' code here.

Limitations

Immutable Only

Pierce only work with immutable data. Mutability is not supported at all because I'm pretty sure it would be impossible to implement soundly. (If you have an idea please share.)

Requires StableDeref

Pointer wrapped by Pierce must be StableDeref. If your pointer type meets the conditions required, you can unsafe impl StableDeref for T {} on it. The trait is re-exported at pierce::StableDeref.

The vast majority of pointers are StableDeref, including Box, Vec, String, Rc, Arc.

You might also like...
Abstract over the atomicity of reference-counting pointers in rust

Archery Archery is a rust library that offers a way to abstraction over Rc and Arc smart pointers. This allows you to create data structures where the

A number of collections, such as linked-lists, binary-trees, or B-Trees are most easily implemented with aliasing pointers.

StaticRc is a safe reference-counted pointer, similar to Rc or Arc, though performing its reference-counting at compile-time rather than run-time, and

Ointers is a library for representing pointers where some bits have been stolen so that they may be used by the programmer for something else

Ointers is a library for representing pointers where some bits have been stolen so that they may be used by the programmer for something else. In effect, it's a small amount of free storage

A simple library with just one struct which is used to wrap around pointers

A simple library with just one struct which is used to wrap around pointers. This can be used to create pointers and share them across threads without the hassle of synchronization if you really do not care about that.

Jsonptr - Data structures and logic for resolving, assigning, and deleting by JSON Pointers

jsonptr - JSON Pointers for Rust Data structures and logic for resolving, assigning, and deleting by JSON Pointers (RFC 6901). Usage Resolve JSON Poin

HP++: A Hazard Pointers Extension for Better Applicability

HP++: A Hazard Pointers Extension for Better Applicability This is an implementation of HP++, a safe memory reclamation scheme proposed in Jaehwang Ju

Thread-safe cell based on atomic pointers to externally stored data

Simple thread-safe cell PtrCell is an atomic cell type that allows safe, concurrent access to shared data. No std, no data races, no nasal demons (UB)

An inquiry into nondogmatic software development. An experiment showing double performance of the code running on JVM comparing to equivalent native C code.
An inquiry into nondogmatic software development. An experiment showing double performance of the code running on JVM comparing to equivalent native C code.

java-2-times-faster-than-c An experiment showing double performance of the code running on JVM comparing to equivalent native C code ⚠️ The title of t

Parallel finance a decentralized lending protocol built on top of the Polkadot ecosystem. Our unique approach will allow users to earn
Parallel finance a decentralized lending protocol built on top of the Polkadot ecosystem. Our unique approach will allow users to earn "double interests" from staking and lending their tokens simultaneously.

Parallel Finance A new Cumulus-based Substrate node, ready for hacking 🚀 Getting Started Follow these steps to get started with the Cumulus Template

🐎 Daac Horse: Double-Array Aho-Corasick in Rust

🐎 daachorse Daac Horse: Double-Array Aho-Corasick Overview A fast implementation of the Aho-Corasick algorithm using Double-Array Trie. Examples use

Cyclic Double-Linked List

Double-Linked List This crate provides a doubly-linked list with owned nodes, implemented as a cyclic list. Usage First, add dependency in your Cargo.

Pure Rust implementation of the Double Ratchet algorithm
Pure Rust implementation of the Double Ratchet algorithm

Double Ratchet A pure Rust implementation of the Double Ratchet, as specified by Trevor Perrin and Moxie Marlinspike. The Double Ratchet allows two us

Click-once - A small tiny little binary to fix undesired mouse double clicks in Windows, written in Rust.

click-once A small tiny little binary to fix malfunctioning mouse double clicks in Windows, written in Rust. Minimal executable with little to no over

Shellfirm - Intercept any risky patterns (default or defined by you) and prompt you a small challenge for double verification
Shellfirm - Intercept any risky patterns (default or defined by you) and prompt you a small challenge for double verification

shellfirm Opppppsss you did it again? 😱 😱 😰 Protect yourself from yourself! rm -rf * git reset --hard before saving? kubectl delete ns which going

🦞 Rust library of natural language dictionaries using character-wise double-array tries.

🦞 Crawdad: ChaRActer-Wise Double-Array Dictionary Overview Crawdad is a library of natural language dictionaries using character-wise double-array tr

🐎 A fast implementation of the Aho-Corasick algorithm using the compact double-array data structure. (Python wrapper for daachorse)

python-daachorse daachorse is a fast implementation of the Aho-Corasick algorithm using the compact double-array data structure. This is a Python wrap

Create Bitcoin double-spend discouraging bonds on Liquid.

doubletake A tool for creating Bitcoin double-spend punishment bonds on Liquid. WARNING: Don't use this tool for real use cases yet. There are still a

Smart contracts for Ref Finance

Ref Finance Contracts This mono repo contains the source code for the smart contracts of Ref Finance on NEAR. Contracts Contract Reference Description

Skyward Finance smart-contracts

Build and Init ./build.sh near dev-deploy res/skyward.was export CONTRACT_ID=skyward.testnet near call $CONTRACT_ID new --account_id=$CONTRACT_ID Regi

Owner
null
Cyclic Double-Linked List

Double-Linked List This crate provides a doubly-linked list with owned nodes, implemented as a cyclic list. Usage First, add dependency in your Cargo.

Frank King 10 Nov 26, 2021
A Rust macro for writing nested loop expressions

loop_chain A Rust macro for writing nested loop expressions Usage | Examples | Docs Dependencies [dependencies] loop_chain = "0.1.1" Usage For express

Takayuki Maeda 5 Jul 30, 2021
Eternally liquid. Forward compatible. Nested, conditional, & Multi-resourced NFTs.

RMRK Substrate Rust Setup First, complete the basic Rust setup instructions. Run Use Rust's native cargo command to build and launch the template node

RMRK Team 67 Dec 25, 2022
The [cain!] macro is a macro that rewrites sequential Rust branch statements into nested branches

Note! This crate is experimental and under development. It may include bugs that alter the behavior of your code in unexpected ways. You should review

Fredrik Østrem 2 Jan 19, 2022
Deserialize (potentially nested) environment variables into your custom structs

envious allows you to deserialize your serde enabled structs from environment variables. See it in action: use serde::{Deserialize, Serialize}; #[der

Marcel Müller 46 Feb 19, 2023
A starter template for actix-web projects that feels very Django-esque. Avoid the boring stuff and move faster.

Jelly A.K.A, the actix-web starter you probably wish you had. This is provided as-is, and anyone is free to extend it or rework it as they desire - ju

SecretKeys 198 Dec 15, 2022
argmax is a library that allows Rust applications to avoid Argument list too long errors (E2BIG) by providing a std::process::Command wrapper with a

argmax argmax is a library that allows Rust applications to avoid Argument list too long errors (E2BIG) by providing a std::process::Command wrapper w

David Peter 22 Nov 20, 2022
Slitter is a C- and Rust-callable slab allocator implemented primarily in Rust, with some C for performance or to avoid unstable Rust features.

Slitter is a less footgunny slab allocator Slitter is a classically structured thread-caching slab allocator that's meant to help write reliable long-

Backtrace Labs 133 Dec 5, 2022
A place to start when building webgl apps in Bevy. Use this to avoid writing the boilerplate.

Template Bevy project with WebGL enabled Prerequisites cargo install cargo-make Build and serve WASM version Set your local ip address in Makefile.to

Michael Dorst 0 Dec 24, 2021
A re-write of polkadot staking miner using subxt to avoid hard dependency to each runtime version

Staking Miner v2 WARNING this library is under active development DO NOT USE IN PRODUCTION. The library is a re-write of polkadot staking miner using

Parity Technologies 19 Dec 28, 2022