Повышение производительности плоских буферов в c ++ - PullRequest
0 голосов
/ 28 мая 2020

Мы разрабатываем платформу высокочастотной торговли на C ++ и пробовали реализовать grp c с protobuf, но мы увидели, что один сетевой вызов занимает примерно 200-300 микросекунд, что для нас слишком долго. То, что мы ожидаем от сериализации / десериализации данных через сетевой сокет, составляет приблизительно 50-60 микросекунд. После того, как мы попробовали использовать protobuf с собственными сокетами C ++ (с использованием неблокирующего ввода-вывода), мы увидели, что на этот раз производительность стала примерно 150-200 микросекунд, что было недостаточно для нас. Затем мы увидели флетбуферы и реализовали их, как описано ниже. Однако во время наших тестов мы увидели, что только сериализация (также как и десериализация) занимает примерно 50 микросекунд, а также передача данных занимает 30-40 микросекунд, так что в целом это занимает примерно 100-150 микросекунд. Поэтому я подумал, не делаем ли мы что-то не так в нашей реализации плоских буферов.

В приведенном ниже примере я вычислил разницу между журналами отметок времени:

Отметка времени 1 -> Отметка времени 2 = 16 микросекунд

Отметка времени 2 -> Отметка времени 3 = 24 микросекунды

Общая сериализация = 40 микросекунд

Знаете ли вы какой-либо другой способ повышения производительности

Пример кода для сериализации данных с плоскими буферами в 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;
}

А также это часть нашей схемы в плоских буферах

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;

1 Ответ

1 голос
/ 28 мая 2020

Для сериализации:

  • Вы можете сэкономить время, повторно используя FlatBufferBuilder, просто вызовите Reset () для очистки.
  • Вы выполняете HFT на C ++, еще много ваших данных состоит из строк? FlatBuffers имеет всевозможные действительно эффективные способы представления данных с помощью скаляров, структур и перечислений. Если скорость действительно имеет значение, попытайтесь найти лучшее представление ваших данных.

Для десериализации:

  • Десериализация в FlatBuffers стоит 0 мс, так как ничего делать не нужно. Вы можете получить доступ на месте. Если вы копируете все входящие данные FlatBuffers в свои собственные структуры данных, вы теряете одно из самых больших преимуществ FlatBuffers. Вместо этого заставьте код, воздействующий на входящие данные, работать напрямую с входящим FlatBuffer.
...