Mar*_*rkB 5 c++ protocol-buffers
我的应用程序使用协议缓冲区并具有大量(1 亿)条简单消息。基于 callgrind 分析,正在为每个实例进行内存分配和释放。
考虑以下代表性示例:
// .proto
syntax = "proto2";
package testpb;
message Top {
message Nested {
optional int32 val1 = 1;
optional int32 val2 = 2;
optional int32 val3 = 3;
}
repeated Nested data = 1;
}
// .cpp
void test()
{
testpb::Top top;
for (int i = 0; i < 100'000; ++i) {
auto* data = top.add_data();
data->set_val1(i);
data->set_val2(i*2);
data->set_val3(i*3);
}
std::ofstream ofs{"file.out", std::ios::out | std::ios::trunc | std::ios::binary };
top.SerializeToOstream(&ofs);
}
Run Code Online (Sandbox Code Playgroud)
更改实现以使内存分配数量与实例数量不成线性关系的最有效选项是什么Nested?
我建议使用专门为此目的而设计的竞技场分配。 https://developers.google.com/protocol-buffers/docs/reference/arena
内存分配和释放占了协议缓冲区代码中花费的 CPU 时间的很大一部分。默认情况下,protocol buffers 为每个消息对象、它的每个子对象以及多种字段类型(例如字符串)执行堆分配。当解析消息和在内存中构建新消息时,这些分配会批量发生,并且当释放消息及其子对象树时,会发生相关的释放。
基于竞技场的分配旨在降低这种性能成本。通过arena分配,新对象是从称为arena的一大块预分配内存中分配的。通过丢弃整个 arena 可以立即释放所有对象,理想情况下不需要运行任何包含的对象的析构函数(尽管 arena 仍然可以在需要时维护“析构函数列表”)。通过将其减少为简单的指针增量,这使得对象分配速度更快,并且释放几乎是免费的。Arena分配还提供了更高的缓存效率:当解析消息时,它们更有可能被分配在连续内存中,这使得遍历消息更有可能命中热缓存线。
为了获得这些好处,您需要了解对象的生命周期并找到使用竞技场的合适粒度(对于服务器,这通常是每个请求)。您可以在使用模式和最佳实践中找到有关如何充分利用竞技场分配的更多信息。
这会改变你的分配看起来更像
google::protobuf::Arena arena;
testpb::Top* top = google::protobuf::Arena::CreateMessage<testpb::Top>(&arena);
Run Code Online (Sandbox Code Playgroud)