tolgatanriverdi
tolgatanriverdi

Reputation: 581

Incrase flatbuffers performance in c++

We're developing a high frequency trading platform with C++ and we've tried implementing grpc with protobuf but we saw that a single network call tooks approximately 200-300 microseconds which is too long for us. What we are expecting to have as serializing/deserializing data through network socket is approximately 50-60 microseconds. Than we 've tried to use protobuf with native c++ sockets (with using non blocking i/o), we saw that this time performance became approximately 150-200 microseconds which was not enough for us. Than we saw flatbuffers and implemented it as described in below. However during our tests we saw that only serializing (also same in deserializing) tooks approximately 50 microseconds and also transferring the data tooks 30-40 microseconds so totatly it tooks approximately 100-150 microseconds. So I wondered if we are doing something wrong in our implementation of flatbuffers.

In the below example, I've calculated the difference betwen timestamp logs are :

Timestamp 1 -> Timestamp 2 = 16 microseconds

Timestamp 2 -> Timestamp 3 = 24 microseconds

Total serialization = 40 microseconds

Do you know any other way to increase the performance

Example code for serializing data with flatbuffers in C++:

const char* MAHelper::getRequest(BaseRequest *request,int& size) {
    const char *result;

    flatbuffers::FlatBufferBuilder builder(10240);
    if (request->orderType == OrderTypes::TYPE_LoginRequest){
        std::cout<<"Timestamp 1: "<<getCurrentTimestamp()<<std::endl;
        LoginRequest *loginRequest = (LoginRequest*) request;
        std::cout<<"Converting Login Request 1: "<<getCurrentTimestamp()<<std::endl;
        auto username = builder.CreateString(loginRequest->userName);
        auto password = builder.CreateString(loginRequest->password);
        auto application = getApplication(loginRequest->applicationType);

        std::cout<<"Timestamp 2: "<<getCurrentTimestamp()<<std::endl;
        auto loginReq = piramit::orders::fb::CreateLoginRequest(builder,username,password,application);
        auto loginOrderBase = piramit::orders::fb::CreateRequestHolder(builder,piramit::orders::fb::BaseRequest_LoginRequest,loginReq.Union());
        builder.Finish(loginOrderBase);
        std::cout<<"Timestamp 3:"<<getCurrentTimestamp()<<std::endl;
    } else if (request->orderType == OrderTypes::TYPE_EnterOrderRequest) {
        EnterOrderRequest *enterOrderRequest = (EnterOrderRequest*) request;
        auto strategyIdentifier = builder.CreateString(enterOrderRequest->strategyIdentifier);
        auto passThrough  = builder.CreateString(enterOrderRequest->passThrough);
        auto account = builder.CreateString(enterOrderRequest->account);
        auto authToken = builder.CreateString(enterOrderRequest->baseRequest.authToken);

        auto enterOrderReq = piramit::orders::fb::CreateEnterOrder(builder,enterOrderRequest->orderbookId,enterOrderRequest->quantity,enterOrderRequest->price,account,
                getStrategyType(enterOrderRequest->strategyType),strategyIdentifier,getSide(enterOrderRequest->side),getTimeInForce(enterOrderRequest->timeInForce),passThrough,getOrderType(enterOrderRequest->orderType));
        auto enterOrderBase = piramit::orders::fb::CreateRequestHolder(builder,piramit::orders::fb::BaseRequest_EnterOrder,enterOrderReq.Union(),authToken);
        builder.Finish(enterOrderBase);
    } else if (request->orderType == OrderTypes::TYPE_ReplaceOrderRequest) {
        ReplaceOrderRequest  *replaceOrderRequest = (ReplaceOrderRequest*) request;
        auto orderToken = builder.CreateString(replaceOrderRequest->orderToken);
        auto authToken = builder.CreateString(replaceOrderRequest->baseRequest.authToken);

        auto replaceOrderReq = piramit::orders::fb::CreateReplaceOrder(builder,orderToken,replaceOrderRequest->quantity,replaceOrderRequest->price);
        auto replaceOrderBase = piramit::orders::fb::CreateRequestHolder(builder,piramit::orders::fb::BaseRequest_ReplaceOrder,replaceOrderReq.Union(),authToken);
        builder.Finish(replaceOrderBase);
    } else if (request->orderType == OrderTypes::TYPE_CancelOrderRequest) {
        CancelOrderRequest  *cancelOrderRequest = (CancelOrderRequest*) request;
        auto orderToken = builder.CreateString(cancelOrderRequest->orderToken);
        auto authToken = builder.CreateString(cancelOrderRequest->baseRequest.authToken);

        auto cancelOrderReq = piramit::orders::fb::CreateCancelOrder(builder,orderToken);
        auto cancelOrderBase = piramit::orders::fb::CreateRequestHolder(builder,piramit::orders::fb::BaseRequest_CancelOrder,cancelOrderReq.Union(),authToken);
        builder.Finish(cancelOrderBase);
    } else if (request->orderType == OrderTypes::TYPE_BasicOrderRequest) {
        BasicOrderRequest  *basicOrderRequest = (BasicOrderRequest*) request;
        auto authToken = builder.CreateString(basicOrderRequest->baseRequest.authToken);

        auto basicOrderReq = piramit::orders::fb::CreateOrderRequest(builder,getOperationType(basicOrderRequest->operation),basicOrderRequest->orderId,getOrderType(basicOrderRequest->orderTypes));
        auto basicOrderBase = piramit::orders::fb::CreateRequestHolder(builder,piramit::orders::fb::BaseRequest_OrderRequest,basicOrderReq.Union(),authToken);
        builder.Finish(basicOrderBase);
    } else if (request->orderType == OrderTypes::TYPE_AccountStrategyRequest) {
        AccountStrategyRequest  *accountStrategyRequest = (AccountStrategyRequest*) request;

        flatbuffers::Offset<flatbuffers::String> account = 0;
        flatbuffers::Offset<flatbuffers::String> strategyIdentifier = 0;
        auto authToken = builder.CreateString(accountStrategyRequest->baseRequest.authToken);

        if (accountStrategyRequest->operation == OPERATION_SET) {
            account = builder.CreateString(accountStrategyRequest->accountStrategy.account);
            strategyIdentifier = builder.CreateString(accountStrategyRequest->accountStrategy.strategyIdentifier);
        }
        flatbuffers::Offset<piramit::orders::fb::AccountStrategy> accountStrategy = piramit::orders::fb::CreateAccountStrategy(builder,accountStrategyRequest->accountStrategy.orderBookId,account,getStrategyType(accountStrategyRequest->accountStrategy.strategyType),strategyIdentifier);

        auto accountStrategyReq = piramit::orders::fb::CreateAccountStrategyRequest(builder,getOperationType(accountStrategyRequest->operation),accountStrategy);
        auto accountStrategyBase = piramit::orders::fb::CreateRequestHolder(builder,piramit::orders::fb::BaseRequest_AccountStrategyRequest,accountStrategyReq.Union(),authToken);
        builder.Finish(accountStrategyBase);
    } else if (request->orderType == OrderTypes::TYPE_OrderBookStateRequest) {
        OrderBookStateRequest  *orderBookStateRequest = (OrderBookStateRequest*) request;

        auto stateName = builder.CreateString(orderBookStateRequest->stateName);
        auto orderBookStateReq = piramit::orders::fb::CreateOrderBookStateRequest(builder,stateName,orderBookStateRequest->orderBookId,orderBookStateRequest->timestamp);
        auto orderBookStateBase = piramit::orders::fb::CreateRequestHolder(builder,piramit::orders::fb::BaseRequest_OrderBookStateRequest,orderBookStateReq.Union());
        builder.Finish(orderBookStateBase);
    }

    uint8_t *requestBuffer = builder.GetBufferPointer();
    result = (const char*) requestBuffer;
    size = builder.GetSize();

    return result;
}

And also this is part of our schema in flatbuffers

union BaseRequest { LoginRequest,EnterOrder,CancelOrder,ReplaceOrder,OrderRequest,AccountStrategyRequest,OrderBookStateRequest }

table RequestHolder  {
  request:BaseRequest;
  authToken:string;
}

table LoginRequest {
    username:string;
    password:string;
    application:Application = APP_UNKNOWN;
}

table EnterOrder{
    order_book_id:uint;
    quantity:ulong;
    price:int;
    account:string;
    strategy:StrategyType;
    strategy_identifier:string;
    side:Side;
    time_in_force:TimeInForce;
    pass_through:string;
    order_type:OrderType;
}

root_type RequestHolder;

Upvotes: 0

Views: 871

Answers (1)

Aardappel
Aardappel

Reputation: 6074

For serializing:

  • You can save yourself some time by reusing the FlatBufferBuilder accross, just call Reset() on it to clear.
  • You are doing HFT in C++, yet a lot of your data consists of strings? FlatBuffers has all sorts of really efficient ways of representing data, with scalars, structs and enums. Try to find better representations of your data if speed really matters.

For deserializing:

  • Deserializing in FlatBuffers costs 0ms, since there is no need to do anything. You can access in place. If what you're doing is copying all incoming FlatBuffers data into your own data structures, you are throwing away one of FlatBuffers biggest advantages. Instead, make the code acting on the incoming data work directly with the incoming FlatBuffer.

Upvotes: 2

Related Questions