0%

C++ 内存复用_内存池_allocator

最近几天写一些服务端的基础小工具学习到不少内容, 特别是内存管理这块

这里记录一下,C++ 中的内存复用

new/delete 的局限性

写 C++ 的孩子都知道 C++ 中使用动态内存 (堆内存), 一般使用 new 和 delete 这对关键字

与 malloc/free 不同,new 申请内存后还会初始化内存空间,调用构造函数; delete 会先调用析构函数,之后释放内存

一般我们都是需要对象的时候 new 一个,用完后 delete 掉, 但是如果一种类型的对象会很频繁的被使用到,就会有大量的 new/delete 操作

new 操作符内部一般用 malloc 实现,malloc 向系统申请内存空间会有系统调用,如果很频繁的 new/delete 会导致用户态和内核态切换较多,浪费性能,而且容易产生大量内存碎片

对于需要频繁使用的类对象,如果能重用一片内存区域, 就不会有上述问题

std::allocator 的使用 (C++11)

C++ 11 提供了 std::allocator 类模板, 它是所有标准库容器的默认分配器,默认分配器无状态,即任何给定的 allocator 实例可交换,比较像等,且能解分配同一 allocator 类型的任何其他实例所分配的内存

上面的介绍来自 cppreference 文档 std::allocator - cppreference.com

不理解也无所谓,只用知道它最大的作用就是 可以将内存的申请和类对象初始化, 还有类对象析构和内存释放拆分开

也就是说,使用 allocator 可以申请一个对象的内存空间之后, 可以在这片相同的内存上多次构造 / 析构不同的对象,也就是内存重用

测试类

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
class Test
{
public:
static int index;
Test()
{
x = ++index;
str = str + std::to_string(x);
std::cout << "Test Constructor() this=" << this << std::endl;
}
~Test()
{
std::cout << "Test Destructor() this=" << this << std::endl;
}
void Show()
{
std::cout << "Test show x(" << x << "), str(" << str << ")" << std::endl;
}
private:
int x;
std::string str {"sssaaa"};
};


int Test::index = 0;

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int main()
{
std::allocator<Test> alloc;
printf("allocate ------------ \n");
Test* p = alloc.allocate(1); // 申请内存
printf("construct ----------- \n");
alloc.construct(p); // 构造对象
printf("use object ------------ \n");
p->Show(); // 使用对象
printf("destruct ------------ \n");
alloc.destroy(p); // 销毁对象
// 同一片内存的再次使用
printf("construct ----------- \n");
alloc.construct(p); // 构造对象
printf("use object ------------ \n");
p->Show(); // 使用对象
printf("destruct ------------ \n");
alloc.destroy(p); // 销毁对象
// 释放内存
printf("deallocate ------------ \n");
alloc.deallocate(p, 1);
return 0;

}

结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
allocate ------------
construct -----------
Test Constructor() this=0x1d1a50
use object ------------
Test show x(1), str(sssaaa1)
destruct ------------
Test Destructor() this=0x1d1a50
construct -----------
Test Constructor() this=0x1d1a50
use object ------------
Test show x(2), str(sssaaa2)
destruct ------------
Test Destructor() this=0x1d1a50
deallocate ------------

其中内存的申请 / 释放使用 mallocfree 效果也一样

std::allocator_traits 的使用 (C++11, C++17, C++20)

在 C++17 中,std::allocator 类模板的 construct destroy 等方法被标记为弃用,到 C++20 这些方法直接被移除了,所以上面直接使用 std::allocator 的源码在 C++20 中是无法使用的

标准库中提供了一个 std::allocator_traits 类模板提供几个静态方法,用于标准化使用 std::allocator

其实就是套个壳,不允许直接使用

将测试代码改成如下形式,则可以在 C++20 中正常使用

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
int main()
{
std::allocator<Test> alloc;
std::allocator_traits<std::allocator<Test>> traits;
printf("allocate ------------ \n");
Test* p = traits.allocate(alloc, 1); // 申请内存
printf("construct ----------- \n");
traits.construct(alloc, p); // 构造对象
printf("use object ------------ \n");
p->Show(); // 使用对象
printf("destruct ------------ \n");
traits.destroy(alloc, p); // 销毁对象
// 同一片内存的再次使用
printf("construct ----------- \n");
traits.construct(alloc, p); // 构造对象
printf("use object ------------ \n");
p->Show(); // 使用对象
printf("destruct ------------ \n");
traits.destroy(alloc, p); // 销毁对象
// 释放内存
printf("deallocate ------------ \n");
traits.deallocate(alloc, p, 1);
return 0;

}

内存池

头文件

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
#ifndef _OBJECT_POOL_H_
#define _OBJECT_POOL_H_

#include <unordered_map>
#include <memory>
#include <list>
#include <string>
#include <functional>

class IRecyclable
{
public:
virtual std::string Type() = 0;
virtual void OnUse() = 0;
virtual void OnRecycle() = 0;
};

class ObjectAlloctor
{
public:
std::function<IRecyclable*()> m_allocate;
std::function<void(IRecyclable*)> m_construct;
std::function<void(IRecyclable*)> m_destroy;
std::function<void(IRecyclable*)> m_deallocate;
};

class ObjectPool
{
public:
static ObjectPool Instance;
~ObjectPool() { ClearAll(); }
public:
template<class T, class... Args>
void SetType(const std::string& type, Args &&... args);
IRecyclable* GetObj(const std::string& type);
void ReturnObj(IRecyclable* obj);
void ClearAll();
private:
IRecyclable* CreateObj(const std::string& type);
private:
std::unordered_map<std::string, std::list<IRecyclable*>> m_mapObjs;
std::unordered_map<std::string, ObjectAlloctor> m_mapAlloc;
private:
ObjectPool() {};
ObjectPool(ObjectPool&) = delete;
ObjectPool(ObjectPool&&) = delete;
const ObjectPool& operator=(const ObjectPool&) = delete;
const ObjectPool& operator=(ObjectPool&&) = delete;
};

template<class T, class... Args>
void ObjectPool::SetType(const std::string& type, Args&&... args)
{
m_mapAlloc[type].m_allocate = [] {
return (IRecyclable*)(std::malloc(sizeof(T)));
};
// 这里需要注意, 闭包函数的参数都是引用传递, 如果是临时变量需要用值传递或者特殊处理一下 比如在函数内再申明变量保存一下参数
m_mapAlloc[type].m_construct = [&args...](IRecyclable* p) {
std::allocator<T> alloc;
std::allocator_traits<std::allocator<T>> traits;
traits.construct(alloc, static_cast<T*>(p), std::move(args)...);
};
m_mapAlloc[type].m_destroy = [](IRecyclable* p) {
std::allocator<T> alloc;
std::allocator_traits<std::allocator<T>> traits;
traits.destroy(alloc, static_cast<T*>(p));
};
m_mapAlloc[type].m_deallocate = [](IRecyclable* p) {
std::free(p);
};
}

#endif

注意: C++ 中不支持模板的分离编译 , 模板的实现都写在头文件中

源文件

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
#include "ObjectPool.h"

ObjectPool ObjectPool::Instance;

IRecyclable* ObjectPool::GetObj(const std::string& type)
{
IRecyclable* obj = nullptr;
auto objIt = m_mapObjs.find(type);
if (objIt != m_mapObjs.end() && objIt->second.size() > 0)
{
obj = objIt->second.front();
objIt->second.pop_front();
}
if (obj == nullptr)
{
obj = CreateObj(type);
}
auto allocIt = m_mapAlloc.find(type);
if (allocIt == m_mapAlloc.end()) { throw "somethings wrong"; }
allocIt->second.m_construct(obj);
obj->OnUse();
return obj;
}

IRecyclable* ObjectPool::CreateObj(const std::string& type)
{
IRecyclable* obj = nullptr;
auto allocIt = m_mapAlloc.find(type);
if (allocIt == m_mapAlloc.end()) { throw "somethings wrong"; }
obj = allocIt->second.m_allocate();
return obj;
}

void ObjectPool::ReturnObj(IRecyclable* obj)
{
if (obj == nullptr) { return; }
std::string type = obj->Type();
auto it = m_mapAlloc.find(type);
if (it == m_mapAlloc.end())
{
throw "try to return obj that it's type not setted";
}
obj->OnRecycle();
it->second.m_destroy(obj);
m_mapObjs[type].push_back(obj);
}

void ObjectPool::ClearAll()
{
for (auto it = m_mapObjs.begin(); it != m_mapObjs.end(); ++it)
{
auto allocIt = m_mapAlloc.find(it->first);
if (allocIt == m_mapAlloc.end()) { throw "somethings wrong"; }
for (auto lstit = it->second.begin(); lstit != it->second.end(); ++lstit)
{
allocIt->second.m_deallocate(*lstit);
}
}
m_mapObjs.clear();
}

测试

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
class Test : public IRecyclable
{
public:
static int index;
virtual std::string Type() override { return "test"; }
virtual void OnUse() override { std::cout << "OnUse this=" << this << std::endl; }
virtual void OnRecycle() override { std::cout << "OnRecycle this=" << this << std::endl; }
Test()
{
x = ++index;
str = str + std::to_string(x);
std::cout << "Test Constructor() this=" << this << std::endl;
}
~Test()
{
std::cout << "Test Destructor() this=" << this << std::endl;
}
void Show()
{
std::cout << "Test show x(" << x << "), str(" << str << ")" << std::endl;
}
private:
int x;
std::string str {"sssaaa"};
};

int Test::index = 0;
1
2
3
4
5
6
7
8
9
10
11
12
13
int main(int, char**)
{
ObjectPool::Instance.SetType<Test>(std::string("test"));
Test* t1 = (Test*)ObjectPool::Instance.GetObj("test");
Test* t2 = (Test*)ObjectPool::Instance.GetObj("test");
t1->Show();
t2->Show();
ObjectPool::Instance.ReturnObj((IRecyclable*)t1);
Test* t3 = (Test*)ObjectPool::Instance.GetObj("test");
t3->Show();
ObjectPool::Instance.ReturnObj((IRecyclable*)t2);
ObjectPool::Instance.ReturnObj((IRecyclable*)t3);
}

测试结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Test Constructor() this=0x8037fe0
OnUse this=0x8037fe0
Test Constructor() this=0x8038430
OnUse this=0x8038430
Test show x(1), str(sssaaa1)
Test show x(2), str(sssaaa2)
OnRecycle this=0x8037fe0
Test Destructor() this=0x8037fe0
Test Constructor() this=0x8037fe0
OnUse this=0x8037fe0
Test show x(3), str(sssaaa3)
OnRecycle this=0x8038430
Test Destructor() this=0x8038430
OnRecycle this=0x8037fe0
Test Destructor() this=0x8037fe0