|
这篇是protobuf 提供的c++ API (部分Arena源码)学习记录。
protobuf c++ API 分四个部分: ① google::protobuf ②google::protobuf::io ③google::protobuf::util ④google::protobuf::compiler
google::protobuf
说明:这部分是protocol buffer 运行时库的核心组件。另外,官方文档说除了特殊声明,只要对象在所有线程中都是const的,使用起来是线程安全的;如果不允许以const方式访问某个对象,则是线程不安全的。
Arena解决的问题:默认情况下,proto为对象,为其子对象,或者其他的一些类型(string bytes)都会在堆上申请内存(这里我还没有去看message的构造机制,Todo),因此构造新消息或者解析消息会出现多次堆内存申请,析构时有多次堆内存释放。
使用Arena后,有效避免上述问题,对象的内存是从Arena预申请的一片大内存空间中获取,在Arena释放时对象被统一释放。将频繁的申请、释放内存的行为改变成指针偏移, 效率提高。另外缓存效率更高,原因是在解析消息时,从Arena获取连续的内存,遍历消息时更有机会命中缓存。【参考官方文档】
Arena Allocator for better allocation performance.(Arena的分配性能更佳) 相关实现文件:arena.h http://arean.cc arena_impl.h 相关测试文件:arena_test_util.h http://arena_unittest.cc
arena.h 中的类:
- ArenaOptions
内存块申请选项,比如Arena初始申请内存大小、最大内存限制,初始化使用的内存块,内存块申请函数、释放函数
- Arena
Allocator
- Arena::InternalHelper
Arena的内部类, 通过模板元编程的方式判断类型是否满足Arena构造条件
- Arena::is_arena_constructable
类型支持Arena
- Arena::is_destructor_skippable
类型支持不执行析构函数,并且是安全的
Arena API:
- Arena()
无参构造函数,创建Arena
- Arena(const ArenaOptions& options)
使用上述的特定选项创建Arena, 可以实现在向系统申请内存前,使用用户提供的内存,进行内存申请限制,自定义申请、释放函数
- template static T* CreateMessage(Arena * arena)
在给定的arena上创建proto对象,proto文件必须开启Arena;arena 非空,则返回在arena上创建的对象,注意这个对象是不能被手动释放的;如果arena是空,对象将和不用Arena时一样在堆上创建
- template static T* Create(Arena* arena, args…)
可以创建任何对象,不仅仅是proto对象,包括C++自定义类
- template static T* CreateArray(Arena* arena, size_t n)
arena非空情况下从Arena上申请N个对象空间大小,并且T的构造函数不会被调用,否则在堆上如普通创建
- template void Own(T* object)
将对象添加到arena的堆对象列表中。arena释放时,会遍历这个列表对每个对象进行delete。函数适用于不在arena上创建的对象,但是该对象的生存期又需要绑定到arena上的情况,(比如父message支持arena,submessage不支持arena在另一个proto文件中)
- template void OwnDestructor(T* object)
将对象的析构器放入arena的destructor列表中,(列表与Own列表应该不同) ,arena释放时,遍历列表调用保存的destructor。函数适用于对象使用的是Arena内存,但是析构函数不会被调用的情况,比如proto对象
- uint64 SpaceUsed() const
返回arena的大小
- uint64 Reset()
销毁arena,调用所有注册的析构函数,释放所有注册的堆对象(那两个Own列表),与arena的销毁过程类似,只是会reset内存,重新使用。这个函数非线程安全的
- template Arena* GetArena()
返回指向arena的指针
使用Arena, 需要设置option cc_enable_arenas = true; 下面说明启用Arena后,接口实现发生的变化。
对于Message提供接口发生一些变化:
- Message(Message&& other)
如果源消息不在arena上,移动构造函数会移动other所有的字段,无需拷贝和申请堆内存。如果源消息在arena上,则会进行深拷贝
- Message& operator=(Message&& other)
如果两个消息都不在arena上,将会进行移动构造。如果仅有其中之一在arena上,或者两个消息不在同一arena上,将会进行深拷贝
- void Swap(Message* other)
都没有使用arena或者都在同一arena上,通过交换指针避免拷贝开销。如果一个消息在arena上,而另一个不在,或者两个消息在不同的arena上,进行深拷贝。否则交换的子对象可能会有不同的生命周期(可以想象一下仅交换头指针的情况)
- Message* New(Arena* arena)
在给定的arena上创建对象。如果开启了cc_enable_arenas,函数与CreateMessage表现一致;否则相当于在普通内存申请,然后调用arena->Own(message)
- Arena* GetArena()
返回message所在arena指针
- void UnsafeArenaSwap(Message* other)
不对message是否在同一arena上做检查,比较高效。使用这个函数的时候要确保两个message在同一arena上,否则出现不可预知的结果
对于嵌套Filed接口发生的变化,例如:
optional Bar foo = 1;
required Bar foo = 1;
- Bar* mutable_foo()
返回mutable指针,如果父对象在arena上,则子对象也在arena上
- void set_allocated_foo(Bar* bar)
将字段置为新对象的值。如果父对象在arena上,子对象在堆上,将子对象arena->own放入堆对象列表;如果两者不在同一arena上,会将message的拷贝置为新值;如果父子对象在同一arena上或者都在堆上,函数与未使用arena相同
- Bar* release_foo()
返回存在的子对象或者空指针,将归属权释放给调用者,清除父对象的该字段。如果父对象在arena上,将在堆上拷贝子对象,返回这个拷贝
- void unsafe_arena_set_allocated_foo(Bar* bar)
假设父子对象都在同一arena上,进行allocated
- Bar* unsafe_arena_release_foo()
假设父对象在arena上,返回在arena上的子对象,该函数只适用于父对象在arena的情况
对于String Field
即使父对象在arena上,string仍然在堆上申请内存。
对于Repeated Field
- void UnsafeArenaSwap(RepeatedPtrField* other)
unsafe版本的swap不会验证将要进行交换的RepeatedPtrField,是否拥有指向相同arena的指针
- void Swap(RepeatedPtrField* other)
进行arena指针检查,没有指向同一arena,则进行复制,然后swap
- void AddAllocated(SubMessageType* value)
subMessage和父message如果在同一arena上,直接sub的指针放入array;如果sub在堆上,释放原始的sub,将拷贝的副本放入array
- SubMessageType* ReleaseLast()
返回repeated字段最后一个对象,如果repeated字段在堆上,没有使用arena,直接返回原始的sub;否则在堆上进行拷贝,然后返回
- void UnsafeArenaAddAllocated(SubMessageType* value)
不进行堆或者arena的检查,直接放入array,调用者需要保证(如果repeated Field在arena上,那么value需在同一arena上或者有相同的生命周期,或者该field在堆上)
- void ExtractSubrange(int start, int num, SubMessageType** elements)
移除num个元素,如果元素在arena上,返回前会先拷贝到堆上
- void UnsafeArenaExtractSubrange(int start, int num, SubMessageType** elements)
移除num个元素,但是从不进行拷贝
官方文档例子说明:
message MyFeatureMessage {
optional string feature_name = 1;
repeated int32 feature_data = 2;
optional NestedMessage nested_message = 3;
};
Arena* arena = new google::protobuf::Arena();
MyFeatureMessage* arena_message_1 = google::protobuf::Arena::CreateMessage<MyFeatureMessage>(arena);
arena_message_1->mutable_nested_message()->set_feature_id(11);
MyFeatureMessage* arena_message_2 = google::protobuf::Arena::CreateMessage<MyFeatureMessage>(arena);
arena_message_2->set_allocated_nested_message(arena_message_1->release_nested_message());
arena_message_1->release_message();//returns a copy of the underlying nested_message and deletes underlying pointer
使用Arena后,有可能导致无意识的拷贝,可以参考上面的API说明,unsafe的函数是为了避免拷贝的。
arena_message_2->set_allocated_nested_message(arena_message_1->unsafe_arena_release_nested_message());swap低效用法:
MyFeatureMessage* message_1 = google::protobuf::Arena::CreateMessage<MyFeatureMessage>(arena);
message_1->mutable_nested_message()->set_feature_id(11);
MyFeatureMessage* message_2 = new MyFeatureMessage;
message_2->mutable_nested_message()->set_feature_id(22);
message_1->Swap(message_2); // Inefficient swap!swap高效用法,在相同的arena上创建对象,避免拷贝:
MyFeatureMessage* message_2 = google::protobuf::Arena::CreateMessage<MyFeatureMessage>(arena);源码一些细节:
参考资料:
https://developers.google.cn/protocol-buffers/docs/reference/arenas 官方文档
https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/arena.h 源码 |
|