How is binary serialisation done so fast? #1774
Replies: 4 comments 3 replies
-
|
This is surprising and suspicious, but I was surprised by a performance test last week, so I won't immediately rule out something wrong in the test.
If you could share a simple example of the kind of test you are doing I could run some benchmarks as well. Make sure you are just profiling time and not MB/s, because the BEVE output requires more memory. |
Beta Was this translation helpful? Give feedback.
-
|
@stephenberry Ahh, never mind. It took me ages but I figured out that it was a warm cache issue. It's really hard to get things right with benchmarking |
Beta Was this translation helpful? Give feedback.
-
|
@stephenberry Hi. These are the results I got. reflect-cpp uses yyjson: Time for reflect-cpp to output minified json = 256.851 microseconds Time for reflect-cpp to read minified json = 124.319 microseconds Time to do first memcpy = 2.671 microseconds Time to do second memcpy = 2.256 microseconds Time to serialise to Glaze minified JSON format = 39.754 microseconds Time to deserialise Glaze minified JSON format = 48.574 microseconds Time to serialise to BEVE untagged binary format = 10.179 microseconds Time to deserialise BEVE untagged binary format = 11.854 microseconds Although it should be noted that the yyjson test isn't comparable because the reflect-cpp api returns a new std::string buffer, so there's no way to warm the caches. My binary serialisation method is the same Glaze's, there's no way to get faster. I noticed that when, let's just say you're serialising/deserialising a vector/array, if the element type of the array is trivially copyable you can do it four times faster by memcpying the entire array, but outside of that there are no performance improvements, and I actually think it's risky because for a number of reasons the type might not be trivially memcpyable, or become non trivially memcpyable at a later stage. Or you've written out little endian and the machine is big-endian (actually I'm not even sure this is even worth worrying about nowadays). I had two other questions.
Then it's the deserialiser's job to destroy the any already existing objects, right? Is there a way to deserialise without default constructing first? I think there is but it gets complicated and I haven't seriously looked into it. |
Beta Was this translation helpful? Give feedback.
-
|
@stephenberry Just another question. If I have a std::vector that already has objects in it, that I pass as a buffer to glz::read_json to deserialise, what does it do with it? It has to destroy each object before it deserialises something. Does it call .clear() to destroy them, then default construct an object with push back or what? |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
I tried serialising an std::vector where MyType is a trivially copyable type, and therefore can be individually serialised with memcpy, or indeed the entire array/vector can be just memcpied with sizeof(MyType) * my_vector.size(). I compared glz::write_beve_untagged with a simple memcpy of the entire vector, and my timings consistently show that glz::write_beve_untagged is faster by between 3 and 5 times. This makes absolutely no sense and have spent a long time trying to figure out why, including reversing the order of the tests so that it might show a difference with respect to the caches 'being warm', but it's not the case, glz::write_beve_untagged is always faster than a simple memcpy, and it makes no sense.
Furthermore, Glaze doesn't compress the types, and I'm pretty sure doesn't use memcpy for the entire vector itself, but loops through each object and serialises the type for each member of the vector. This makes absolutely no sense. Please explain what might be happening.
Beta Was this translation helpful? Give feedback.
All reactions