Home

Awesome

Fast Binary Encoding (FBE)

Awesome C++ License Release <br/> Linux (clang) Linux (gcc) MacOS <br/> Windows (Cygwin) Windows (MSYS2) Windows (MinGW) Windows (Visual Studio)

Fast Binary Encoding allows to describe any domain models, business objects, complex data structures, client/server requests & responses and generate native code for different programming languages and platforms.

Fast Binary Encoding documentation<br/> Fast Binary Encoding downloads<br/> Fast Binary Encoding specification

Performance comparison to other protocols can be found here:

ProtocolMessage sizeSerialization timeDeserialization time
Cap'n'Proto208 bytes558 ns359 ns
FastBinaryEncoding234 bytes66 ns82 ns
FlatBuffers280 bytes830 ns290 ns
Protobuf120 bytes628 ns759 ns
JSON301 bytes740 ns500 ns

Typical usage workflow is the following:

  1. Create domain model using base types, enums, flags and structs
  2. Generate domain model for any supported programming languages (C++, C#, Go, Java, JavaScript, Kotlin, Python, Ruby, Swift)
  3. Build domain model library
  4. Serialize/Deserialize objects from the domain model in unified, fast and compact FastBinaryEncoding (FBE) format
  5. JSON convert objects from the domain model in order to use them in Web API
  6. Implement Sender/Receiver interfaces to create a communication protocol

Sample projects:

Contents

Features

Requirements

Optional:

How to build?

Linux: install required packages

sudo apt-get install -y binutils-dev uuid-dev flex bison

MacOS: install required packages

brew install flex bison

Windows: install required packages

choco install winflexbison3

Install gil (git links) tool

pip3 install gil

Setup repository

git clone https://github.com/chronoxor/FastBinaryEncoding.git
cd FastBinaryEncoding
gil update

Linux

cd build
./unix.sh

MacOS

cd build
./unix.sh

Windows (Cygwin)

cd build
unix.bat

Windows (MSYS2)

cd build
unix.bat

Windows (MinGW)

cd build
mingw.bat

Windows (Visual Studio)

cd build
vs.bat

Create domain model

To use Fast Binary Encoding you should provide a domain model (aka business objects). A domain model is a set of enums, flags and structures that relate to each other and might be aggregated in some hierarchy.

Fast Binary Encoding (FBE) format specification

There is a sample domain model which describes Account-Balance-Orders relation of some abstract trading platform:

// Package declaration
package proto

// Domain declaration
domain com.chronoxor

// Order side declaration
enum OrderSide : byte
{
    buy;
    sell;
}

// Order type declaration
enum OrderType : byte
{
    market;
    limit;
    stop;
}

// Order declaration
struct Order
{
    [key] int32 uid;
    string symbol;
    OrderSide side;
    OrderType type;
    double price = 0.0;
    double volume = 0.0;
}

// Account balance declaration
struct Balance
{
    [key] string currency;
    double amount = 0.0;
}

// Account state declaration
flags State : byte
{
    unknown = 0x00;
    invalid = 0x01;
    initialized = 0x02;
    calculated = 0x04;
    broken = 0x08;
    good = initialized | calculated;
    bad = unknown | invalid | broken;
}

// Account declaration
struct Account
{
    [key] int32 uid;
    string name;
    State state = State.initialized | State.bad;
    Balance wallet;
    Balance? asset;
    Order[] orders;
}

Generate domain model

The next step is a domain model compilation using 'fbec' compiler which will create a generated code for required programming language.

The following command will create a C++ generated code:

fbec --c++ --input=proto.fbe --output=.

All possible options for the 'fbec' compiler are the following:

Usage: fbec [options]

Options:
  --version             show program's version number and exit
  -h, --help            show this help message and exit
  -h HELP, --help=HELP  Show help
  -i INPUT, --input=INPUT
                        Input path
  -o OUTPUT, --output=OUTPUT
                        Output path
  -q, --quiet           Launch in quiet mode. No progress will be shown!
  -n INDENT, --indent=INDENT
                        Format indent. Default: 0
  -t, --tabs            Format with tabs. Default: off
  --cpp                 Generate C++ code
  --cpp-logging         Generate C++ logging code
  --csharp              Generate C# code
  --go                  Generate Go code
  --java                Generate Java code
  --javascript          Generate JavaScript code
  --kotlin              Generate Kotlin code
  --python              Generate Python code
  --ruby                Generate Ruby code
  --swift               Generate Swift code
  --final               Generate Final serialization code
  --json                Generate JSON serialization code
  --proto               Generate Sender/Receiver protocol code

Build domain model

Generated domain model is represented with source code for the particular language. Just add it to your project and build it. There are several issues and dependencies that should be mentioned:

C++

C#

Go

Java

JavaScript

Kotlin

Python

Ruby

gem install json
gem install uuidtools

Swift

FBE serialization

Fast Binary Encoding (FBE) is a fast and compact binary format of representing single domain models in different programming languages and platforms. Also FBE format solves protocol versioning problem.

Follow the steps below in order to serialize any domain object:

  1. Create a new domain object and fill its fields and collections (proto::Account account);
  2. Create a domain model with a write buffer (FBE::proto::AccountModelFBE::WriteBuffer writer)
  3. Serialize the domain object into the domain model buffer (writer.serialize(account))
  4. (Optional) Verify the domain object in the domain model buffer (assert(writer.verify()))
  5. Access the domain model buffer to store or send data (writer.buffer())

Follow the steps below in order to deserialize any domain object:

  1. Create a domain model with a read buffer (FBE::proto::AccountModelFBE::ReadBuffer reader)
  2. Attach a source buffer to the domain model (reader.attach(writer.buffer()))
  3. (Optional) Verify the domain object in the domain model buffer (assert(reader.verify()))
  4. Deserialize the domain object from the domain model buffer (reader.deserialize(account))

Here is an exmple of FBE serialization in C++ language:

#include "../proto/proto_models.h"

#include <iostream>

int main(int argc, char** argv)
{
    // Create a new account with some orders
    proto::Account account = { 1, "Test", proto::State::good, { "USD", 1000.0 }, std::make_optional<proto::Balance>({ "EUR", 100.0 }), {} };
    account.orders.emplace_back(1, "EURUSD", proto::OrderSide::buy, proto::OrderType::market, 1.23456, 1000.0);
    account.orders.emplace_back(2, "EURUSD", proto::OrderSide::sell, proto::OrderType::limit, 1.0, 100.0);
    account.orders.emplace_back(3, "EURUSD", proto::OrderSide::buy, proto::OrderType::stop, 1.5, 10.0);

    // Serialize the account to the FBE stream
    FBE::proto::AccountModel<FBE::WriteBuffer> writer;
    writer.serialize(account);
    assert(writer.verify());

    // Show the serialized FBE size
    std::cout << "FBE size: " << writer.buffer().size() << std::endl;

    // Deserialize the account from the FBE stream
    FBE::proto::AccountModel<FBE::ReadBuffer> reader;
    reader.attach(writer.buffer());
    assert(reader.verify());
    reader.deserialize(account);

    // Show account content
    std::cout << std::endl;
    std::cout << account;

    return 0;
}

Output is the following:

FBE size: 252

Account(
  uid=1,
  name="Test",
  state=initialized|calculated|good,
  wallet=Balance(currency="USD",amount=1000),
  asset=Balance(currency="EUR",amount=100),
  orders=[3][
    Order(uid=1,symbol="EURUSD",side=buy,type=market,price=1.23456,volume=1000),
    Order(uid=2,symbol="EURUSD",side=sell,type=limit,price=1,volume=100),
    Order(uid=3,symbol="EURUSD",side=buy,type=stop,price=1.5,volume=10)
  ]
)

FBE final serialization

It is possible to achieve more serialization speed if your protocol is mature enough so you can fix its final version and disable versioning which requires extra size and time to process.

ProtocolMessage sizeSerialization timeDeserialization timeVerify time
FBE252 bytes88 ns98 ns33 ns
FBE final152 bytes57 ns81 ns28 ns

Final domain model can be compiled with --final flag. As the result additional final models will be available for serialization.

Follow the steps below in order to serialize any domain object in final format:

  1. Create a new domain object and fill its fields and collections (proto::Account account);
  2. Create a domain final model with a write buffer (FBE::proto::AccountFinalModelFBE::WriteBuffer writer)
  3. Serialize the domain object into the domain model buffer (writer.serialize(account))
  4. (Optional) Verify the domain object in the domain model buffer (assert(writer.verify()))
  5. Access the domain model buffer to store or send data (writer.buffer())

Follow the steps below in order to deserialize any domain object:

  1. Create a domain final model with a read buffer (FBE::proto::AccountFinalModelFBE::ReadBuffer reader)
  2. Attach a source buffer to the domain final model (reader.attach(writer.buffer()))
  3. (Optional) Verify the domain object in the domain model buffer (assert(reader.verify()))
  4. Deserialize the domain object from the domain model buffer (reader.deserialize(account))

Here is an exmple of FBE final serialization in C++ language:

#include "../proto/proto_models.h"

#include <iostream>

int main(int argc, char** argv)
{
    // Create a new account with some orders
    proto::Account account = { 1, "Test", proto::State::good, { "USD", 1000.0 }, std::make_optional<proto::Balance>({ "EUR", 100.0 }), {} };
    account.orders.emplace_back(1, "EURUSD", proto::OrderSide::buy, proto::OrderType::market, 1.23456, 1000.0);
    account.orders.emplace_back(2, "EURUSD", proto::OrderSide::sell, proto::OrderType::limit, 1.0, 100.0);
    account.orders.emplace_back(3, "EURUSD", proto::OrderSide::buy, proto::OrderType::stop, 1.5, 10.0);

    // Serialize the account to the FBE stream
    FBE::proto::AccountFinalModel<FBE::WriteBuffer> writer;
    writer.serialize(account);
    assert(writer.verify());

    // Show the serialized FBE size
    std::cout << "FBE final size: " << writer.buffer().size() << std::endl;

    // Deserialize the account from the FBE stream
    FBE::proto::AccountFinalModel<FBE::ReadBuffer> reader;
    reader.attach(writer.buffer());
    assert(reader.verify());
    reader.deserialize(account);

    // Show account content
    std::cout << std::endl;
    std::cout << account;

    return 0;
}

Output is the following:

FBE final size: 152

Account(
  uid=1,
  name="Test",
  state=initialized|calculated|good,
  wallet=Balance(currency="USD",amount=1000),
  asset=Balance(currency="EUR",amount=100),
  orders=[3][
    Order(uid=1,symbol="EURUSD",side=buy,type=market,price=1.23456,volume=1000),
    Order(uid=2,symbol="EURUSD",side=sell,type=limit,price=1,volume=100),
    Order(uid=3,symbol="EURUSD",side=buy,type=stop,price=1.5,volume=10)
  ]
)

JSON serialization

If the domain model compiled with --json flag, then JSON serialization code will be generated in all domain objects. As the result each domain object can be serialized/deserialized into/from JSON format.

Please note that some programming languages have native JSON support (JavaScript, Python). Other languages requires third-party library to get work with JSON:

Here is an exmple of JSON serialization in C++ language:

#include "../proto/proto.h"

#include <iostream>

int main(int argc, char** argv)
{
    // Create a new account with some orders
    proto::Account account = { 1, "Test", proto::State::good, { "USD", 1000.0 }, std::make_optional<proto::Balance>({ "EUR", 100.0 }), {} };
    account.orders.emplace_back(1, "EURUSD", proto::OrderSide::buy, proto::OrderType::market, 1.23456, 1000.0);
    account.orders.emplace_back(2, "EURUSD", proto::OrderSide::sell, proto::OrderType::limit, 1.0, 100.0);
    account.orders.emplace_back(3, "EURUSD", proto::OrderSide::buy, proto::OrderType::stop, 1.5, 10.0);

    // Serialize the account to the JSON stream
    rapidjson::StringBuffer buffer;
    rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
    FBE::JSON::to_json(writer, account);

    // Show the serialized JSON and its size
    std::cout << "JSON: " << buffer.GetString() << std::endl;
    std::cout << "JSON size: " << buffer.GetSize() << std::endl;

    // Parse the JSON document
    rapidjson::Document json;
    json.Parse(buffer.GetString());

    // Deserialize the account from the JSON stream
    FBE::JSON::from_json(json, account);

    // Show account content
    std::cout << std::endl;
    std::cout << account;

    return 0;
}

Output is the following:

JSON: {
  "uid":1,
  "name":
  "Test",
  "state":6,
  "wallet":{"currency":"USD","amount":1000.0},
  "asset":{"currency":"EUR","amount":100.0},
  "orders":[
    {"uid":1,"symbol":"EURUSD","side":0,"type":0,"price":1.23456,"volume":1000.0},
    {"uid":2,"symbol":"EURUSD","side":1,"type":1,"price":1.0,"volume":100.0},
    {"uid":3,"symbol":"EURUSD","side":0,"type":2,"price":1.5,"volume":10.0}
  ]
}
JSON size: 353

Account(
  uid=1,
  name="Test",
  state=initialized|calculated|good,
  wallet=Balance(currency="USD",amount=1000),
  asset=Balance(currency="EUR",amount=100),
  orders=[3][
    Order(uid=1,symbol="EURUSD",side=buy,type=market,price=1.23456,volume=1000),
    Order(uid=2,symbol="EURUSD",side=sell,type=limit,price=1,volume=100),
    Order(uid=3,symbol="EURUSD",side=buy,type=stop,price=1.5,volume=10)
  ]
)

Packages and import

Packages are declared with package name and structs offset (optional). Offset will be add to incremented structure type if is was not provided explicit.

Here is an example of the simple package declaration:

// Package declaration. Offset is 0.
package proto

// Struct type number is 1 (proto offset 0 + 1)
struct Struct1
{
    ...
}

// Struct type number is 2 (proto offset 0 + 2)
struct Struct2
{
    ...
}

One package can be imported into another and all enums, flags and structs can be reused in the current package. Package offset is used here to avoid structs types intersection:

// Package declaration. Offset is 10.
package protoex offset 10

// Package import
import proto

// Struct type number is 11 (protoex offset 10 + 1)
struct Struct11
{
    // Struct1 is reused form the imported package
    proto.Struct1 s1;
    ...
}

// Struct type number is 12 (protoex offset 10 + 2)
struct Struct12
{
    ...
}

Multiple package import is possible as well:

// Package declaration. Offset is 100.
package test offset 100

// Package import
import proto
import protoex

...

Package import is implemented using:

Struct keys

Some of struct fileds (one or many) can be marked with '[key]' attribute. As the result corresponding compare operators will be generated which allow to compare two instances of the struct (equality, ordering, hashing) by marked fields. This ability allows to use the struct as a key in associative map and hash containers.

Example below demonstrates the usage of '[key]' attribute:

struct MyKeyStruct
{
    [key] int32 uid;
    [key] stirng login;
    string name;
    string address;
}

After code generation for C++ language the following comparable class will be generated:

struct MyKeyStruct
{
    int32_t uid;
    ::sample::stirng login;
    std::string name;
    std::string address;

    ...

    bool operator==(const MyKeyStruct& other) const noexcept
    {
        return (
            (uid == other.uid)
            && (login == other.login)
            );
    }
    bool operator!=(const MyKeyStruct& other) const noexcept { return !operator==(other); }
    bool operator<(const MyKeyStruct& other) const noexcept
    {
        if (uid < other.uid)
            return true;
        if (other.uid < uid)
            return false;
        if (login < other.login)
            return true;
        if (other.login < login)
            return false;
        return false;
    }
    bool operator<=(const MyKeyStruct& other) const noexcept { return operator<(other) || operator==(other); }
    bool operator>(const MyKeyStruct& other) const noexcept { return !operator<=(other); }
    bool operator>=(const MyKeyStruct& other) const noexcept { return !operator<(other); }

    ...
};

Struct numeration

Struct type numbers are automatically increased until you provide it manually. There are two possibilities:

  1. Shift the current struct type number using '(+X)' suffix. As the result all new structs will have incremented type.
  2. Force set struct type number using '(X)' of '(base)' suffix. It will affect only one struct.

Example below demonstrates the idea:

// Package declaration. Offset is 0.
package proto

// Struct type number is 1 (implicit declared)
struct Struct1
{
    ...
}

// Struct type number is 2 (implicit declared)
struct Struct2
{
    ...
}

// Struct type number is 10 (explicit declared, shifted to 10)
struct Struct10(+10)
{
    ...
}

// Struct type number is 11 (implicit declared)
struct Struct11
{
    ...
}

// Struct type number is 100 (explicit declared, forced to 100)
struct Struct100(100)
{
    ...
}

// Struct type number is 12 (implicit declared)
struct Struct12
{
    ...
}

Struct inheritance

Structs can be inherited from another struct. In this case all fields from the base struct will be present in a child one.

package proto

// Struct type number is 1
struct StructBase
{
    bool f1;
    int8 f2;
}

// Struct type number is 2
struct StructChild : StructBase
{
    // bool f1 - will be inherited from StructBase
    // int8 f2 - will be inherited from StructBase
    int16 f3;
    int32 f4;
}

Also it is possible to reuse the base struct type number in a child one using '= base' operator. It is useful when you extend the struct from third-party imported package:

// Package declaration. Offset is 10.
package protoex offset 10

// Package import
import proto

// Struct type number is 1
struct StructChild(base) : proto.StructBase
{
    // bool f1 - will be inherited from proto.StructBase
    // int8 f2 - will be inherited from proto.StructBase
    int16 f3;
    int32 f4;
}

Versioning

Versioning is simple with Fast Binary Encoding.

Assume you have an original protocol:

package proto

enum MyEnum
{
    value1;
    value2;
}

flags MyFlags
{
    none = 0x00;
    flag1 = 0x01;
    flag2 = 0x02;
    flag3 = 0x04;
}

struct MyStruct
{
    bool field1;
    byte field2;
    char field3;
}

You need to extend it with new enum, flag and struct values. Just add required values to the end of the corresponding declarations:

package proto

enum MyEnum
{
    value1;
    value2;
    value3; // New value
    value4; // New value
}

flags MyFlags
{
    none = 0x00;
    flag1 = 0x01;
    flag2 = 0x02;
    flag3 = 0x04;
    flag4 = 0x08; // New value
    flag5 = 0x10; // New value
}

struct MyStruct
{
    bool field1;
    byte field2;
    char field3;
    int32 field4;          // New field (default value is 0)
    int64 field5 = 123456; // New field (default value is 123456)
}

Now you can serialize and deserialize structs in different combinations:

Versioning of the third-party protocol

If you are not able to modify some third-party protocol, you can still have a solution of extending it. Just create a new protocol and import third-party one into it. Then extend structs with inheritance:

package protoex

import proto

struct MyStructEx(base) : proto.MyStruct
{
    int32 field4;          // New field (default value is 0)
    int64 field5 = 123456; // New field (default value is 123456)
}

Sender/Receiver protocol

If the domain model compiled with --sender flag, then Sender/Receiver protocol code will be generated.

Sender interface contains 'send(struct)' methods for all domain model structs. Also it has abstract 'onSend(data, size)' method which should be implemented to send serialized data to a socket, pipe, etc.

Receiver interface contains 'onReceive(struct)' handlers for all domain model structs. Also it has public 'onReceive(type, data, size)' method which should be used to feed the Receiver with received data from a socket, pipe, etc.

Here is an exmple of using Sender/Receiver communication protocol in C++ language:

#include "../proto/proto_protocol.h"

#include <iostream>

class MySender : public FBE::proto::Sender<FBE::WriteBuffer>
{
protected:
    size_t onSend(const void* data, size_t size) override
    {
        // Send nothing...
        return 0;
    }

    void onSendLog(const std::string& message) const override
    {
        std::cout << "onSend: " << message << std::endl;
    }
};

class MyReceiver : public FBE::proto::Receiver<FBE::WriteBuffer>
{
protected:
    void onReceive(const proto::Order& value) override {}
    void onReceive(const proto::Balance& value) override {}
    void onReceive(const proto::Account& value) override {}

    void onReceiveLog(const std::string& message) const override
    {
        std::cout << "onReceive: " << message << std::endl;
    }
};

int main(int argc, char** argv)
{
    MySender sender;

    // Enable logging
    sender.logging(true);

    // Create and send a new order
    proto::Order order = { 1, "EURUSD", proto::OrderSide::buy, proto::OrderType::market, 1.23456, 1000.0 };
    sender.send(order);

    // Create and send a new balance wallet
    proto::Balance balance = { "USD", 1000.0 };
    sender.send(balance);

    // Create and send a new account with some orders
    proto::Account account = { 1, "Test", proto::State::good, { "USD", 1000.0 }, std::make_optional<proto::Balance>({ "EUR", 100.0 }), {} };
    account.orders.emplace_back(1, "EURUSD", proto::OrderSide::buy, proto::OrderType::market, 1.23456, 1000.0);
    account.orders.emplace_back(2, "EURUSD", proto::OrderSide::sell, proto::OrderType::limit, 1.0, 100.0);
    account.orders.emplace_back(3, "EURUSD", proto::OrderSide::buy, proto::OrderType::stop, 1.5, 10.0);
    sender.send(account);

    MyReceiver receiver;

    // Enable logging
    receiver.logging(true);

    // Receive all data from the sender
    receiver.receive(sender.buffer().data(), sender.buffer().size());

    return 0;
}

Output is the following:

onSend: Order(uid=1,symbol="EURUSD",side=buy,type=market,price=1.23456,volume=1000)
onSend: Balance(currency="USD",amount=1000)
onSend: Account(uid=1,name="Test",state=initialized|calculated|good,wallet=Balance(currency="USD",amount=1000),asset=Balance(currency="EUR",amount=100),orders=[3][Order(uid=1,symbol="EURUSD",side=buy,type=market,price=1.23456,volume=1000),Order(uid=2,symbol="EURUSD",side=sell,type=limit,price=1,volume=100),Order(uid=3,symbol="EURUSD",side=buy,type=stop,price=1.5,volume=10)])
onReceive: Order(uid=1,symbol="EURUSD",side=buy,type=market,price=1.23456,volume=1000)
onReceive: Balance(currency="USD",amount=1000)
onReceive: Account(uid=1,name="Test",state=initialized|calculated|good,wallet=Balance(currency="USD",amount=1000),asset=Balance(currency="EUR",amount=100),orders=[3][Order(uid=1,symbol="EURUSD",side=buy,type=market,price=1.23456,volume=1000),Order(uid=2,symbol="EURUSD",side=sell,type=limit,price=1,volume=100),Order(uid=3,symbol="EURUSD",side=buy,type=stop,price=1.5,volume=10)])

Performance benchmarks

All benchmarks use the same domain model to create a single account with three orders:

Account account = { 1, "Test", State::good, { "USD", 1000.0 }, std::make_optional<Balance>({ "EUR", 100.0 }), {} };
account.orders.emplace_back(1, "EURUSD", OrderSide::buy, OrderType::market, 1.23456, 1000.0);
account.orders.emplace_back(2, "EURUSD", OrderSide::sell, OrderType::limit, 1.0, 100.0);
account.orders.emplace_back(3, "EURUSD", OrderSide::buy, OrderType::stop, 1.5, 10.0);

Benchmark 1: Serialization

Serialization benchmark C++ code:

BENCHMARK_FIXTURE(SerializationFixture, "Serialize")
{
    // Reset FBE stream
    writer.reset();

    // Serialize the account to the FBE stream
    writer.serialize(account);
}

Serialization benchmark results:

Language & PlatformMessage sizeSerialization rateSerialization time
C++ Win64252 bytes10 416 667 ops/s96 ns
C++ Win64 (Final)152 bytes16 129 032 ops/s62 ns
C++ Win64 (JSON)353 bytes926 784 ops/s1 079 ns
C# Win64252 bytes1 432 665 ops/s698 ns
C# Win64 (Final)152 bytes1 597 444 ops/s626 ns
C# Win64 (JSON)341 bytes434 783 ops/s2 300 ns
Go Win64252 bytes2 739 726 ops/s365 ns
Go Win64 (Final)152 bytes2 949 852 ops/s339 ns
Go Win64 (JSON)341 bytes258 732 ops/s3 865 ns
Java Win64252 bytes4 247 162 ops/s236 ns
Java Win64 (Final)152 bytes4 883 205 ops/s205 ns
Java Win64 (JSON)353 bytes213 983 ops/s4 673 ns
JavaScript Win64252 bytes93 416 ops/s10 705 ns
JavaScript Win64 (Final)152 bytes112 665 ops/s8 876 ns
JavaScript Win64 (JSON)341 bytes217 637 ops/s4 595 ns
Kotlin Win64252 bytes3 546 694 ops/s282 ns
Kotlin Win64 (Final)152 bytes4 096 406 ops/s244 ns
Kotlin Win64 (JSON)353 bytes185 788 ops/s5 382 ns
Python Win64252 bytes9 434 ops/s105 999 ns
Python Win64 (Final)152 bytes11 635 ops/s85 945 ns
Python Win64 (JSON)324 bytes61 737 ops/s16 198 ns
Ruby Win64252 bytes23 013 ops/s43 453 ns
Ruby Win64 (Final)152 bytes33 361 ops/s29 975 ns
Ruby Win64 (JSON)353 bytes50 842 ops/s19 669 ns
Swift macOS252 bytes74 002 ops/s13 513 ns
Swift macOS (Final)152 bytes100 755 ops/s9 925 ns
Swift macOS (JSON)353 bytes18 534 ops/s53 953 ns

Benchmark 2: Deserialization

Deserialization benchmark C++ code:

BENCHMARK_FIXTURE(DeserializationFixture, "Deserialize")
{
    // Deserialize the account from the FBE stream
    reader.deserialize(deserialized);
}

Deserialization benchmark results:

Language & PlatformMessage sizeDeserialization rateDeserialization time
C++ Win64252 bytes9 523 810 ops/s105 ns
C++ Win64 (Final)152 bytes10 989 011 ops/s91 ns
C++ Win64 (JSON)353 bytes1 375 516 ops/s727 ns
C# Win64252 bytes1 014 199 ops/s986 ns
C# Win64 (Final)152 bytes1 607 717 ops/s622 ns
C# Win64 (JSON)341 bytes258 532 ops/s3 868 ns
Go Win64252 bytes1 510 574 ops/s662 ns
Go Win64 (Final)152 bytes1 540 832 ops/s649 ns
Go Win64 (JSON)341 bytes251 825 ops/s3 971 ns
Java Win64252 bytes2 688 084 ops/s372 ns
Java Win64 (Final)152 bytes3 036 020 ops/s329 ns
Java Win64 (JSON)353 bytes308 675 ops/s3 240 ns
JavaScript Win64252 bytes133 892 ops/s7 469 ns
JavaScript Win64 (Final)152 bytes292 273 ops/s3 422 ns
JavaScript Win64 (JSON)341 bytes289 417 ops/s3 455 ns
Kotlin Win64252 bytes2 280 923 ops/s438 ns
Kotlin Win64 (Final)152 bytes2 652 728 ops/s277 ns
Kotlin Win64 (JSON)353 bytes250 524 ops/s3 992 ns
Python Win64252 bytes8 305 ops/s120 411 ns
Python Win64 (Final)152 bytes11 661 ops/s85 758 ns
Python Win64 (JSON)324 bytes48 859 ops/s20 467 ns
Ruby Win64252 bytes24 351 ops/s41 066 ns
Ruby Win64 (Final)152 bytes33 555 ops/s29 802 ns
Ruby Win64 (JSON)353 bytes42 860 ops/s23 331 ns
Swift macOS252 bytes86 288 ops/s11 589 ns
Swift macOS (Final)152 bytes10 3519 ops/s9 660 ns
Swift macOS (JSON)353 bytes17 077 ops/s58 558 ns

Benchmark 3: Verify

Verify benchmark C++ code:

BENCHMARK_FIXTURE(VerifyFixture, "Verify")
{
    // Verify the account
    model.verify();
}

Verify benchmark results:

Language & PlatformMessage sizeVerify rateVerify time
C++ Win64252 bytes31 250 000 ops/s32 ns
C++ Win64 (Final)152 bytes35 714 286 ops/s28 ns
C# Win64252 bytes4 504 505 ops/s222 ns
C# Win64 (Final)152 bytes8 064 516 ops/s124 ns
Go Win64252 bytes8 474 576 ops/s118 ns
Go Win64 (Final)152 bytes9 090 909 ops/s110 ns
Java Win64252 bytes11 790 374 ops/s85 ns
Java Win64 (Final)152 bytes16 205 533 ops/s62 ns
JavaScript Win64252 bytes1 105 627 ops/s905 ns
JavaScript Win64 (Final)152 bytes5 700 408 ops/s175 ns
Kotlin Win64252 bytes8 625 935 ops/s116 ns
Kotlin Win64 (Final)152 bytes13 373 757 ops/s75 ns
Python Win64252 bytes20 825 ops/s48 019 ns
Python Win64 (Final)152 bytes23 590 ops/s42 391 ns
Ruby Win64252 bytes57 201 ops/s17 482 ns
Ruby Win64 (Final)152 bytes74 262 ops/s13 466 ns
Swift macOS252 bytes164 446 ops/s6 081 ns
Swift macOS (Final)152 bytes228 154 ops/s4 383 ns