Measuring SmallVec Footprint with Smallvectune

13 September 2018

Rust is all about paying only for what you use, and gives us plenty tools to eliminate unneeded allocation. One of the tools that is used in a lot of crates (crates.io shows 98 dependent crates) is SmallVec . It is also used in the Rust compiler. I recently got around to speed up the operation of getting a SmallVec from a slice of copyable data. In short, they’re awesome.

SmallVec s use a trick to put data inline if it fits, only spilling into an internal Vec when their inner capacity is exceeded. For maximal flexibility, they get a type parameter that is an array of 1..36, 64, 128, 256, .. up to 220 of the component type. But how to decide on the right array size, especially if you use a lot of different SmallVec s?

First, the size in memory of a SmallVec<[Item; N]> is mem::sizeof<usize>() + max(2 * mem::sizeof<usize>(), mem::sizeof<Item> * N) (there is also four bytes for a discriminant unless you activate the union feature which gets rid of it). Also the spilled variant will incur an allocation of mem::sizeof<Item>() * capacity .

Note that size in memory is not the only thing to optimize for – having a slightly larger SmallVec on stack in exchange for less allocs might be a good thing – but let’s first look at memory, as that’s usually an easy win, and we cannot in general distinguish SmallVec s on stack from those on the heap.

Even so, in a larger application, it’s not trivial to even keep track of the actual capacity distribution of all SmallVec s, and I think having that data would be valuable. So I wrote a small wrapper crate around smallvec to keep track of all capacity changes, logging them to a file so we can summarize them later.

This is the smallvectune crate.

Usage

Let’s take your typical smallvec-using crate. The Cargo.toml has a

[dependencies] # comment this out # smallvec = "0.6.5" # use this instead smallvectune = "0.0.1"

In your lib.rs (if it’s a library crate), change

// don't use directly // extern crate smallvec; // use this instead extern crate smallvectune as smallvec ;

Now set the environment variable SMALLVECTUNE_OUT to somewhere writeable and run the workload you want to analyze. This will get you a CSV file with

item size in bytes;internal array size;+ or -;capacity

The + entries show that a SmallVec was created or resized to the new capacity, the - entries show a SmallVec that was deallocated or resized from the old capacity. This should be enough to keep a running count of the smallvecs at any point in time, and also estimate their memory footprint. We can differentiate use sites by using different internal array sizes.

I intend to use this with the Rust compiler. Stay tuned. Also if you have one of the 98 SmallVec-dependent crates, try it out and tell me what you think!

PS.: Please someone help me with summarizing this data.