Skip to main content

Crate smox

Crate smox 

Source
Expand description

A Box like type that keeps small objects on the stack and can allocate bigger objects as Box, Rc or Arc. DerefMut is copy-on-write when the Smox is backed by Rc or Arc

§Rationale

In generic contexts one often does not know how big the objects will be. If they are small enough they can be kept on the stack for better performance. If they are bigger they need to be allocated on the heap. Smox allows to do this in a transparent way.

§Example

use smox::*;

// For the copy-on-write behavior we need T: Clone
// starting with v0.5 the size is given in `size_of::<usize>` units.
#[derive(Clone)]
struct Generic<T: Clone> {
    value: SmoxRc<T, 2>,
}

impl<T: Clone> Generic<T> {
    fn new(value: T) -> Self {
        Self {
            value: SmoxRc::new(value),
        }
    }
}

// we have the guarantee that Generic<T> will always be at most 16 bytes large.
assert!(std::mem::size_of::<Generic<i32>>() <= std::mem::size_of::<[usize; 2]>());
assert!(std::mem::size_of::<Generic<[i32; 100]>>() <= std::mem::size_of::<[usize; 2]>());

let mut small = Generic::new(42i32);
*small.value += 1;
assert_eq!(*small.value, 43);

let big = Generic::new([0i32;100]);
// Cheap cloning the underlying Rc
let mut big_clone = big.clone();
// Modifying big_clone will trigger a copy-on-write
big_clone.value[0] = 1;

assert_eq!(big.value[0], 0);
assert_eq!(big_clone.value[0], 1);

§Limitations

  • T and the underlying HeapStorage box type must implement Clone
  • Smox does not support dyn trait objects
  • Smox does not support unsized types

§Details

  • In the current implementation, the given SIZE is also the size of a Smox or the size of a box types whatever is bigger. Current stable rust lacks the generic_const_exprs support to constrain the size when heap storage is used to a single pointer. See below for nightly support.
  • The biggest spaces saving are when you can benefit from the copy-on-write behavior of Rc or Arc. Use either of these when there is a chance that the objects will be cloned.
  • When your Smox should stay as small as possible then try the MINIMAL_SIZE as size limit. Although this may not have the best performance, esp when it can not benefit from CoW.

§Status

With v0.5.0 the the API stabilized for the current rust stable, eventually this will be deprecated and superseeded once rust provides better support for const generics as shown in the nightly features. The nightly support is still experimental and may change in future versions.

§Nightly Support

With the nightly feature enabled, Smox uses generic_const_exprs to optimize away unused in-place storage. When T is larger than SIZE, then no space fo in-place storage is reserved, storing on heap requires only a single pointer, when stored in_place it is size_of::<T>().next_multiple_of(std::mem::size_of::<usize>()).

Note: Using Smox with generic type parameters in structs requires adding a where bound [(); optimal_size::<T, SIZE>()]: to propagate the constraint. This is an inherent limitation of generic_const_exprs.

use smox::*;

// On nightly, the in_place storage is optimized to 0 bytes when T > SIZE
// This test verifies the optimization:

// i32 (4 bytes) fits in SIZE=4, stored in_place
// Size = pointer_size (due to union alignment)
let small: SmoxRc<i32, 4> = SmoxRc::new(42);
assert_eq!(std::mem::size_of_val(&small), std::mem::size_of::<[usize; 1]>());

// [i32; 100] (400 bytes) > SIZE=4, stored on heap → ptr size
// Without nightly optimization, this would be 4*size_of::<usize> bytes!
let big: SmoxRc<[i32; 100], 4> = SmoxRc::new([0; 100]);
assert_eq!(std::mem::size_of_val(&big), std::mem::size_of::<usize>());

// Verify values work correctly
let mut s: SmoxRc<i32, 4> = SmoxRc::new(42);
*s += 1;
assert_eq!(*s, 43);

let b: SmoxRc<[i32; 100], 4> = SmoxRc::new([0; 100]);
// Cheap cloning the underlying Rc
let mut b_clone = b.clone();
// Modifying b_clone will trigger a copy-on-write
b_clone[0] = 1;

assert_eq!(b[0], 0);
assert_eq!(b_clone[0], 1);

§Open Issues

The generic_const_exprs feature is far from complete. Eventually the rust team may specify this functionaly in a completely other way. This crate will stabilize when the rust language supports the required features in stable. The old stable implementation will then be abadoned in favor of the new features. The pre-stabilization implementation will stay available in old versions though.

Constants§

DEFAULT_SIZE
The default size limit for in-place storage in Smox. Currently set to 4 pointers wide. This is a compromise between memory wasted and performance, with the disadvantage of making the Smox sometimes bigger than necessary. Future versions will fix this, see ‘nightly’ support.
MINIMAL_SIZE
Size of a pointer, will only inline types that fit within one pointer. Best reduction on the Smox size itself, but is not optimal in general because allocations need some space for metadata and even for slightly larger types you pay with an extra pointer dereference. By using Rc or Arc you still get the copy-on-write benefit.

Traits§

HeapStorage
Trait for heap storage types used by Smox.

Functions§

nightly_enabled
Returns true if the nightly feature is enabled, false otherwise.
optimal_size
Returns the optimal size for in-place storage of T given a size limit SIZE.

Type Aliases§

SmoxArc
Type alias for Smox using Arc as the heap storage type.
SmoxBox
Type alias for Smox using Box as the heap storage type.
SmoxRc
Type alias for Smox using Rc as the heap storage type.

Unions§

Smox
A smart pointer that stores values either in-place (if they fit within SIZE*size_of::<usize> bytes) or on the heap using the specified heap storage type (Box<T>, Rc<T>, or Arc<T>).