Open nolanderc opened 7 months ago
Thank you for this proposal @nolanderc, very appreciated 😃
However, there is one minor issue with the ZERO_COPY
parameter; it should be possible to dynamically determine it from the given Self::Item
. I advise you to look at the Meilisearch use case, which is probably the project that uses heed the most. We use a CboRoaringBitmap
(using RoaringBitmap
s) codec that conditionally stores integers one after the others (when there are not many) or uses the default serialization method.
I am pretty sure we can apply this optimization to the Meilisearch codebase and see the performance gains 🤩
The
BytesEncode
trait is currently defined as follows:https://github.com/meilisearch/heed/blob/34063834019b41740b684a5aa3ebef8f872579f4/heed-traits/src/lib.rs#L20-L26
There are some issues with this definition:
str
,[u8]
,U32<NativeEndian>
, ...) we have to allocate a temporary buffer where the value is written.For example, most architectures in use are Little-Endian (x86, Arm), however, for lexicographic ordering to correctly sort integers, we have to store them in Big-Endian. Since this incurs a byte-swap on Little-Endian architectures we cannot simply return a pointer to the integer itself, instead we have to allocate a temporary buffer where we store the byte-swapped result. That's one heap allocation for each integer, something which could have been done trivially on the stack or in a CPU register.
Optimizations
In most cases we know a-priori the maximum size of the needed allocation:
In some cases it may be preferable to run serialization in two phases:
MDB_RESERVE
).A new trait
With the above in mind, we can make a few changes to the
BytesEncode
trait (naming TBD):Based on the value of
ZERO_COPY
we can decide between either callingbytes_encode
and directly get our slice, or using a pre-allocated buffer withencode_writer
. (If the )We could also consider making
encode_writer
the required method, and definebytes_encode
in terms ofencode_writer
withVec
as ourWrite
r. However, this would be a breaking change.Example Usage
Let's look at how we might implement
Database::put
with the above definition (pseudocode):Note that we can avoid all temporary allocations and intermediate memory copies in all cases except one: if the resulting size of
value
is unknown.