【问题标题】:Incrase flatbuffers performance in c++在 C++ 中提高 flatbuffers 性能
【发布时间】:2020-05-28 12:17:14
【问题描述】:

我们正在使用 C++ 开发一个高频交易平台,并且我们尝试使用 protobuf 实现 grpc,但我们发现单个网络调用大约需要 200-300 微秒,这对我们来说太长了。我们期望通过网络套接字对数据进行序列化/反序列化大约需要 50-60 微秒。 与我们尝试将 protobuf 与本机 c++ 套接字(使用非阻塞 i/o)相比,我们看到这次性能变成了大约 150-200 微秒,这对我们来说还不够。比我们看到 flatbuffers 并按照下面的描述实现它。然而,在我们的测试中,我们看到只有序列化(反序列化也一样)需要大约 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;

【问题讨论】:

  • 每次你在原生套接字之上堆积一个抽象层,你都在增加开销。如果你想减少开销,你只需要卷起袖子,自己打电话给socket()、connect()、read()和write(),正确地实现从套接字读取和写入的正确语义,并且智能地实现为您的特定用例量身定制的缓冲。

标签: c++ serialization flatbuffers


【解决方案1】:

用于序列化:

  • 您可以通过重复使用 FlatBufferBuilder 来节省一些时间,只需对其调用 Reset() 即可清除。
  • 您正在使用 C++ 进行 HFT,但您的很多数据都包含字符串? FlatBuffers 有各种非常有效的方式来表示数据,包括标量、结构和枚举。如果速度真的很重要,请尝试找到更好的数据表示方式。

反序列化:

  • FlatBuffers 中的反序列化需要 0 毫秒,因为无需执行任何操作。您可以就地访问。如果您正在做的是将所有传入的 FlatBuffers 数据复制到您自己的数据结构中,那么您就是在抛弃 FlatBuffers 的最大优势之一。相反,让作用于传入数据的代码直接与传入的 FlatBuffer 一起工作。

【讨论】:

  • 这就是我反序列化 flatbuffer 数据的方式,但它仍然需要大约 50 微秒 const piramit::orders::fb::LoginRequest loginRequest = requestHolder->request_as_LoginRequest();自动登录结果 = std::make_shared(); loginResult->userName = loginRequest->username()->c_str(); loginResult->password = loginRequest->password()->c_str();结果 = (BaseRequest) loginResult.get();
  • 你正在复制一堆字符串并创建一个shared_ptr,我上面所说的所有事情你都不应该这样做。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-07-12
  • 2016-11-09
  • 2014-06-11
  • 1970-01-01
  • 1970-01-01
  • 2021-07-19
  • 1970-01-01
相关资源
最近更新 更多