gRPC C++ 如何拆分长消息传输并减少1GB数据拷贝次数
gRPC大消息传输拷贝优化方案
你当前流程对应的4次拷贝均可针对性优化,具体方案如下:
1. 避免序列化到完整std::string的拷贝
不要使用response.SerializeToString(&str)接口,改用protobuf提供的SerializeToZeroCopyStream零拷贝序列化接口:
- 若使用gRPC原生流式RPC,可以直接序列化到gRPC的发送缓冲区,完全不需要预先在内存中生成完整1GB的序列化结果
- 若要保留手动分片逻辑,可以用
ArrayOutputStream映射预先分配的连续内存块,序列化结果直接写入该内存块,不需要额外拷贝到临时std::string - 如果Response包含大体积
bytes类型字段,可以调用字段的set_aliased_bytes接口直接引用原始二进制数据的内存,序列化时不会额外拷贝该字段内容
2. 避免分片时的payload拷贝
拆分序列化结果时不需要将每个分片的内容单独拷贝到新的内存空间:
- 给分片消息的payload字段调用
set_aliased_bytes,直接绑定原始序列化内存块的对应偏移地址,protobuf只会记录地址不会拷贝数据
3. 避免客户端拼接分片的拷贝
客户端提前计算完整消息的总长度,预先分配对应大小的连续内存:
- 每收到一个分片,直接将payload内容写入预分配内存的对应偏移位置,不需要先存储所有分片的std::string再做拼接,省去拼接拷贝开销
4. 避免反序列化时的内部拷贝
不要使用message.ParseFromString(&concated_str)接口,改用ParseFromZeroCopyStream接口直接读取预分配的连续内存块:
- 对于大体积
bytes字段,同样可以通过aliased_bytes属性直接绑定内存中对应字段的偏移地址,不需要把字段内容拷贝到消息对象的内部内存
额外优化建议
优先选用gRPC原生服务端流式RPC替代手动分片逻辑,不需要自行维护序号、丢包重试、拼接逻辑,gRPC底层原生支持零拷贝读写接口,整体拷贝次数可降到1次以内,性能远高于手动分片实现,且不需要调整gRPC全局4MB的消息大小限制。
内容的提问来源于stack exchange,提问作者Tinyden
相关产品推荐
相关产品推荐

