Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch serialization #1166

Closed
ilblackdragon opened this issue Aug 13, 2019 · 10 comments
Closed

Switch serialization #1166

ilblackdragon opened this issue Aug 13, 2019 · 10 comments

Comments

@ilblackdragon
Copy link
Member

Unfortunately protos are not deterministicly serialized across languages and platforms.
We also not using the main benefits of the protos, e.g. upgradability and backward compatibility. Because if the transaction or block proto changes, all the responsible nodes must compute the same state transition, meaning must run the same code.

Also it's known that Bincode is faster than protos. We also added CBOR for comparison:
https://gist.github.com/ilblackdragon/88ab377bf0da7ff721af80cb8a11fe28

Generally, CBOR has support across many languages and a proper standard. Bincode is more specific to the Rust and still doesn't have formal specification.

@frol
Copy link
Collaborator

frol commented Aug 14, 2019

Here are some more benchmarks: erickt/rust-serialization-benchmarks#7

FlatBuffers do quite well. I recall we have had an issue with bincode performance.

@ilblackdragon
Copy link
Member Author

Bincode - is very basic serialization format but at the same time has performance issues due to visitor pattern inside the serde.

@ilblackdragon
Copy link
Member Author

CBOR is a bit slower than Bincode, and is actually self-descriptive. Which we don't really need (and it adds extra wire bytes).

Alternative is just using a simple serialization scheme:

 u8 | u16 | u32 | u64 | u128 -> write little endian
 [u8; size] -> for _ in 0..size write byte
 Vec<T> -> len() as u32 + for _ in 0..len() write T
 struct -> for each field -> write field

We already use this in store/trie as it gave 2x improvement over bincode.

This is similar to Simple Serialization suggested by Vitalik here - https://github.com/ethereum/beacon_chain/blob/master/ssz/ssz.py

@vgrichina
Copy link
Collaborator

Here are some more benchmarks: erickt/rust-serialization-benchmarks#7

FlatBuffers do quite well. I recall we have had an issue with bincode performance.

FlatBuffers aren't deterministic:
https://groups.google.com/forum/#!msg/flatbuffers/v2RkM3KB1Qw/emQDRTgmCQAJ

@frol
Copy link
Collaborator

frol commented Aug 15, 2019

Just keep in mind that the deserialization should be secure (e.g. if there is a field storing a length of an array, we should check that the number is sane and the array would fit into memory).

@maxzaver
Copy link
Contributor

maxzaver commented Aug 15, 2019

Performance improvement ideas:

Speed-up serialization
Add size_of field to Serializable trait so that we can compute the size and pre-allocate the byte array before serializing object into it;

Speed-up deserialization
Use repr(C) (or rather repr(packed), but this is currently UB rust-lang/rust#27060) for structs/enums to make memory layout deterministic. Then arrange all fields in struct such that primitive types and types composed strictly of primitive types go first and everything else last. Deserialize primitive types and user-defined types all at once by mapping the memory from the byte array directly to struct then deserialize everything else one at a time.
This will remove the need to allocate objects for primitive types just for them to be copied into the struct fields. Read info:
https://doc.rust-lang.org/reference/type-layout.html#the-c-representation
https://doc.rust-lang.org/nomicon/other-reprs.html
https://doc.rust-lang.org/std/index.html#primitives

@frol
Copy link
Collaborator

frol commented Aug 16, 2019

It seems that speedy is the project very similar in the ideas with BORsh. The benchmarks are also promising.

@maxzaver
Copy link
Contributor

It seems like speedy has a different set of priorities than us. Speedy optimizes for speed above all. While our serializer optimizes for consistency of binary representation, safety, and only then speed.

As the result our code does not contain unsafe blocks, while speedy heavily utilizes unsafe but even more so utilizes methods that toy with undefined behavior, such as mem::uninitialized, but also constructing slice from raw memory.

@ilblackdragon
Copy link
Member Author

Started BORsh & implemented it - https://github.com/nearprotocol/borsh

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants