提高 C++ 中的扁平缓冲器性能

Incrase flatbuffers performance in c++

本文关键字:缓冲器 性能 C++ 提高      更新时间:2023-10-16

我们正在开发一个带有C++高频交易平台,并且我们尝试使用protobuf实现grpc,但我们看到单个网络调用大约需要200-300微秒,这对我们来说太长了。我们期望通过网络套接字序列化/反序列化数据大约是 50-60 微秒。 比我们尝试将protobuf与本机C ++套接字一起使用(使用非阻塞I/O(相比,我们看到这次性能大约变成了150-200微秒,这对我们来说还不够。然后我们看到平面缓冲区并实现它,如下所述。 然而,在我们的测试中,我们看到只有序列化(在反序列化中也相同(需要大约 50 微秒,并且传输数据需要 30-40 微秒,因此大约需要 100-150 微秒。所以我想知道我们在实现flatbuffers时是否做错了什么。

在下面的示例中,我计算了时间戳日志之间的差异为:

时间戳 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;
}

这也是我们在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;

对于序列化:

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

对于反序列化:

  • 在 FlatBuffers 中反序列化的成本为 0ms,因为不需要做任何事情。您可以就地访问。如果您正在做的是将所有传入的 FlatBuffers 数据复制到您自己的数据结构中,那么您将放弃 FlatBuffers 最大的优势之一。相反,使作用于传入数据的代码直接与传入的 FlatBuffer 一起使用。