Description
Currently the driver is preallocating buffers for serialization using a much lower bound than it should. In most cases we know exactly how much to allocate, or we know a more accurate lower bound.
In particular, right now for types like &[i32]
of length N only N bytes are preallocated. Instead it should be size_of::<i32i>() * N
for the bytes plus size_of::<i32>
for the tag.
This is at least true for all integer types. Where it's a bit trickier is if we're in a generic context, such as with Vec, &[T], or tuples of T and if T contains data on the heap.
size_of::` is 24, but the string itself may be smaller than 24 characters (or larger, but that's less of an issue).
I would propose two things.
- All integer types have their capacity calculation fixed. This is a trivial change and will reduce allocations considerably for integer types (I have tested this).
- I would suggest adding a new method to
Value
.,size_hint
.size_hint
would return the lower bound for the size of the serialized value (including its tag).
size_hint
would return usize
, with a default implementation returning size_of::(), as that is the lower bound for all values. The default implementation is purely to preserve compatibility with any external implementations of Value.
This also lets types that are relatively expensive to calculate the size of return a lower bound. For example, HashMap<String, T>
could just return (2 * ::size_hint()) + (2 * T::size_hint())
I've already implemented this in a fork.
Here are the results of benchmarking
serialize_lz4_for_iai
Instructions: 10051 (-3.983569%)
L1 Accesses: 12901 (-4.238420%)
L2 Accesses: 32 (No change)
RAM Accesses: 156 (-1.886792%)
Estimated Cycles: 18521 (-3.521384%)
serialize_none_for_iai
Instructions: 2415 (-14.93484%)
L1 Accesses: 3323 (-14.92576%)
L2 Accesses: 14 (No change)
RAM Accesses: 73 (-2.666667%)
Estimated Cycles: 5948 (-9.892441%)
If this is of interest I can cut a PR.
Here's the code. Very minimal changes and, again, nothing breaking.