CyberRT源码分析-对象池

本文最后更新于:3 分钟前

对象池源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <iostream>
#include <memory>
#include <new>
#include <utility>

#define cyber_likely(x) (__builtin_expect((x), 1))
#define cyber_unlikely(x) (__builtin_expect((x), 0))

template <typename T>
class ObjectPool : public std::enable_shared_from_this<ObjectPool<T>> {
public:
using InitFunc = std::function<void(T *)>;
using ObjectPoolPtr = std::shared_ptr<ObjectPool<T>>;

template <typename... Args>
explicit ObjectPool(uint32_t num_objects, Args &&... args);

template <typename... Args>
ObjectPool(uint32_t num_objects, InitFunc f, Args &&... args);

virtual ~ObjectPool();

std::shared_ptr<T> GetObject();

private:
struct Node {
T object;
Node *next;
};

ObjectPool(ObjectPool &) = delete;
ObjectPool &operator=(ObjectPool &) = delete;
void ReleaseObject(T *);

uint32_t num_objects_ = 0;
char *object_arena_ = nullptr;
Node *free_head_ = nullptr;
};

template <typename T>
template <typename... Args>
ObjectPool<T>::ObjectPool(uint32_t num_objects, Args &&... args)
: num_objects_(num_objects) {
const size_t size = sizeof(Node);
object_arena_ = static_cast<char *>(std::calloc(num_objects_, size));
if (object_arena_ == nullptr) {
throw std::bad_alloc();
}

FOR_EACH(i, 0, num_objects_) {
T *obj = new (object_arena_ + i * size) T(std::forward<Args>(args)...);
reinterpret_cast<Node *>(obj)->next = free_head_;
free_head_ = reinterpret_cast<Node *>(obj);
}
}

template <typename T>
template <typename... Args>
ObjectPool<T>::ObjectPool(uint32_t num_objects, InitFunc f, Args &&... args)
: num_objects_(num_objects) {
const size_t size = sizeof(Node);
object_arena_ = static_cast<char *>(std::calloc(num_objects_, size));
if (object_arena_ == nullptr) {
throw std::bad_alloc();
}

FOR_EACH(i, 0, num_objects_) {
T *obj = new (object_arena_ + i * size) T(std::forward<Args>(args)...);
f(obj);
reinterpret_cast<Node *>(obj)->next = free_head_;
free_head_ = reinterpret_cast<Node *>(obj);
}
}

template <typename T>
ObjectPool<T>::~ObjectPool() {
if (object_arena_ != nullptr) {
const size_t size = sizeof(Node);
FOR_EACH(i, 0, num_objects_) {
reinterpret_cast<Node *>(object_arena_ + i * size)->object.~T();
}
std::free(object_arena_);
}
}

template <typename T>
void ObjectPool<T>::ReleaseObject(T *object) {
if (cyber_unlikely(object == nullptr)) {
return;
}

reinterpret_cast<Node *>(object)->next = free_head_;
free_head_ = reinterpret_cast<Node *>(object);
}

template <typename T>
std::shared_ptr<T> ObjectPool<T>::GetObject() {
if (cyber_unlikely(free_head_ == nullptr)) {
return nullptr;
}

auto self = this->shared_from_this();
auto obj =
std::shared_ptr<T>(reinterpret_cast<T *>(free_head_),
[self](T *object) { self->ReleaseObject(object); });
free_head_ = free_head_->next;
return obj;
}

其中涉及到了一些宏:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <type_traits>

DEFINE_TYPE_TRAIT(HasLess, operator<) // NOLINT

template <class Value, class End>
typename std::enable_if<HasLess<Value>::value && HasLess<End>::value,
bool>::type
LessThan(const Value& val, const End& end) {
return val < end;
}

template <class Value, class End>
typename std::enable_if<!HasLess<Value>::value || !HasLess<End>::value,
bool>::type
LessThan(const Value& val, const End& end) {
return val != end;
}

#define FOR_EACH(i, begin, end) \
for (auto i = (true ? (begin) : (end)); \
apollo::cyber::base::LessThan(i, (end)); ++i)
  • 这里auto i = (true ? (begin) : (end))使begin和end都参与模板推导,保证其类型一致
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#define DEFINE_TYPE_TRAIT(name, func)                     \
template <typename T> \
struct name { \
template <typename Class> \
static constexpr bool Test(decltype(&Class::func)*) { \
return true; \
} \
template <typename> \
static constexpr bool Test(...) { \
return false; \
} \
\
static constexpr bool value = Test<T>(nullptr); \
}; \
\
template <typename T> \
constexpr bool name<T>::value;
  • 利用SFINAE原理检测类中是否有指定名称的成员函数,然后生成一个静态布尔值 value

对象池的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
struct Foo {
Foo(int a, int b) :
x(a), y(b)
{}
int x;
int y;
};
int main()
{
// 创建一个池,预分配10个Foo,构造时传两个参数
auto pool = std::make_shared<ObjectPool<Foo>>(10, 42, 100);
// 从池里拿一个对象
auto obj = pool->GetObject();
if (obj) {
std::cout << "Foo: " << obj->x << ", " << obj->y << std::endl;
// 使用完毕后,obj的shared_ptr析构会自动把对象放回池中
} else {
std::cout << "No object available" << std::endl;
}
ObjectPool<Foo>::InitFunc init_func = [](Foo *foo) {
foo->x = 10;
foo->y = 20;
};
// 创建一个池,预分配10个Foo,构造时传初始化函数和两个参数
auto pool2 = std::make_shared<ObjectPool<Foo>>(10, init_func, 0, 0);
auto obj2 = pool2->GetObject();
if (obj2) {
std::cout << "Foo2: " << obj2->x << ", " << obj2->y << std::endl;
// 使用完毕后,obj的shared_ptr析构会自动把对象放回池中
} else {
std::cout << "No object available" << std::endl;
}
return 0;
}

背景与动机

在高性能系统(尤其是自动驾驶、游戏服务器、网络引擎等)中,频繁的 new / delete 会导致:

  • 内存分配/释放开销大
  • 内存碎片化严重
  • 频繁触发系统调用,降低缓存命中率

对象池(Object Pool)是一种经典的优化手段:

  • 一次性预分配大量对象
  • 通过复用对象避免频繁分配/释放
  • 内存布局连续,提升缓存友好性

CyberRT 也采用了对象池来复用通信消息对象,以降低延迟。

核心设计

ObjectPool 模板类主要有几个关键设计:

2.1 内存连续分配

构造函数中一次性分配 num_objects * sizeof(Node) 大小的内存:

1
object_arena_ = static_cast<char *>(std::calloc(num_objects_, size));
  • Node 包含一个 T object 和一个 Node* next 指针(用于维护空闲链表)。
  • 所有对象内存连续,提高缓存局部性

2.2 对象预构造

通过定位 new(placement new)在预分配的内存上构造对象:

1
T *obj = new (object_arena_ + i * size) T(std::forward<Args>(args)...);

好处:

  • 不需要每次 GetObject() 时再构造
  • 对象随时可用,延迟更低

支持两种初始化方式:

  • 默认构造参数(第一个构造函数模板)
  • 自定义初始化函数 InitFunc(第二个构造函数模板)

2.3 空闲对象管理

对象池通过 free_head_ 指针维护一个单向链表的空闲节点:

1
2
reinterpret_cast<Node *>(obj)->next = free_head_;
free_head_ = reinterpret_cast<Node *>(obj);
  • 取对象时,从 free_head_ 取出第一个节点
  • 释放对象时,将节点插回 free_head_

这种LIFO 栈式管理的方式有更好的缓存局部性,因为最近释放的对象更可能被再次使用。


2.4 与 shared_ptr 结合

GetObject() 返回 std::shared_ptr<T>,并且自定义 deleter:

1
2
3
auto obj = std::shared_ptr<T>(
reinterpret_cast<T *>(free_head_),
[self](T *object) { self->ReleaseObject(object); });
  • shared_ptr 的引用计数归零时,会自动调用 ReleaseObject() 将对象放回池中。
  • 调用者不需要关心对象的释放时机,接口简单安全。

这种做法比直接返回裸指针更安全,避免了忘记释放对象导致内存泄漏。


2.5 析构函数释放

~ObjectPool() 中:

  1. 调用每个对象的析构函数(手动调用 ~T()
  2. 释放整块内存:
1
2
reinterpret_cast<Node *>(object_arena_ + i * size)->object.~T();
std::free(object_arena_);

这样确保:

  • 对象的资源(比如内部的动态分配)被正确释放
  • 整块内存一次性归还系统

工作流程

  • 初始化: 向对象池中传递目标的类型和构建目标的参数,用T... Args来进行传递,在构造函数内部首先计算了一个Node的大小,然后分配num_objectsnode大小的内存作为目标池来管理,根据这个node的定义我们可以知道对象池管理对象时是以链表的方式将各个链表链接起来的,再分配完毕内存后,通过FOR_EACH来在指定的地址处创建对象,然后让创建的这个对象的指针指向free_head_

    还有一个重载版本的构造函数,允许在构造对象池的时候提供一个操作对象的函数

  • 释放对象: 将一个对象重新放入对象池中:然后更新free_head的值

  • 获取对象:通过std::shared_ptr<T> ptr(pointer, deleter);来得到这个obj,第二个参数即是智能指针管理的这个对象的引用计数归零时将要执行的操作,这里就是说当obj的引用计数归零时,让此对象重新返回对象池,所以传入的可调用对象的形式是一个lamada函数:[self](T *object) { self->ReleaseObject(object); },然后更新free_head_

  • 析构:遍历调用对象的自己析构函数,然后释放arena这块内存