Skip to content

Commit 32c3d85

Browse files
committed
feat page_cache free part, still with bugs
1 parent e4466d2 commit 32c3d85

File tree

10 files changed

+221
-14
lines changed

10 files changed

+221
-14
lines changed

README.md

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
- [内存申请流程联调](#内存申请流程联调)
2020
- [thread\_cache内存释放](#thread_cache内存释放)
2121
- [central\_cache内存释放](#central_cache内存释放)
22+
- [page\_cache内存释放](#page_cache内存释放)
2223

2324
***
2425

@@ -1110,4 +1111,73 @@ void central_cache::release_list_to_spans(void* start, size_t size) {
11101111

11111112
细节在注释里面写的很清楚了。
11121113

1113-
要注意,调用pc的接口的时候,就记得把桶锁解掉。
1114+
要注意,调用pc的接口的时候,就记得把桶锁解掉。
1115+
1116+
## page_cache内存释放
1117+
1118+
就是这个函数。
1119+
1120+
```cpp
1121+
void page_cache::release_span_to_page(span* s) {
1122+
// 对span前后对页尝试进行合并,缓解内存碎片问题
1123+
}
1124+
```
1125+
1126+
然后刚才的map可以帮助我们查找前后的page。
1127+
1128+
然后我们前后找的时候,要区分这个页是不是在centralCache上的,如果在cc上,那就不能合并。
1129+
1130+
然后这个判断不能用use_count==0这个判断条件。有可能这个span刚从pc拿过来,还没给别人的时候,use_count就是0,这个span,pc是不能回收合并的。
1131+
1132+
所以可以给span添加一个参数is_use就行了。
1133+
1134+
```cpp
1135+
// 管理大块内存
1136+
class span {
1137+
public:
1138+
PAGE_ID __page_id; // 大块内存起始页的页号
1139+
size_t __n = 0; // 页的数量
1140+
// 双向链表结构
1141+
span* __next = nullptr;
1142+
span* __prev = nullptr;
1143+
size_t __use_count = 0; // 切成段小块内存,被分配给threadCache的计数器
1144+
void* __free_list = nullptr; // 切好的小块内存的自由链表
1145+
bool is_use = false; // 是否在被使用
1146+
};
1147+
```
1148+
1149+
然后cc.cc这里改一下,拿到之后改成true就行。
1150+
1151+
cc.cc
1152+
```cpp
1153+
page_cache::get_instance()->__page_mtx.lock();
1154+
span* cur_span = page_cache::get_instance()->new_span(size_class::num_move_page(size));
1155+
cur_span->is_use = true; // 表示已经被使用
1156+
page_cache::get_instance()->__page_mtx.unlock();
1157+
```
1158+
1159+
1160+
然后继续写这个逻辑:
1161+
1162+
page_cache.cc
1163+
```cpp
1164+
void page_cache::release_span_to_page(span* s) {
1165+
// 对span前后对页尝试进行合并,缓解内存碎片问题
1166+
PAGE_ID prev_id = s->__page_id - 1; // 前一块span的id一定是当前span的id-1
1167+
// 拿到id如何找span: 之前写好的map能拿到吗?
1168+
}
1169+
```
1170+
1171+
现在的问题是,之前的map能拿到吗?还拿不到,因为我们之前的map只记录了分给cc的span的映射,没有存留在pc那些,没有分出去的映射。
1172+
所以我们要在`span* page_cache::new_span(size_t k) {`里面添加一下,留在pagecache那些块的映射。
1173+
1174+
```cpp
1175+
// 存储n_span的首尾页号跟n_span的映射,方便pc回收内存时进行合并查找
1176+
__id_span_map[n_span->__page_id] = n_span;
1177+
__id_span_map[n_span->__page_id + n_span->__n - 1] = n_span;
1178+
```
1179+
1180+
为什么这里不用循环存储呢?
1181+
1182+
因为这里的pc的内存只是被span挂起来啊,不会被切啊,所以知道地址就了啊!
1183+
给cc的那些,会被切开变成很多固定大小的内存块啊!所以这里不用循环存。

debug

174 KB
Binary file not shown.

include/common.hpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88
#include <mutex>
99
#include <unordered_map>
1010

11+
#ifdef PROJECT_DEBUG
12+
#include "log.hpp"
13+
#include <iostream>
14+
#endif
15+
1116
#if defined(_WIN32) || defined(_WIN64)
1217
#include <windows.h>
1318
#elif defined(__aarch64__) // ...
@@ -70,9 +75,13 @@ class free_list {
7075
return obj;
7176
}
7277
void pop(void*& start, void*& end, size_t n) {
73-
// 这里是输出参数了
74-
assert(n >= __size);
78+
// 这里是输出参数了
79+
#ifdef PROJECT_DEBUG
80+
LOG(DEBUG) << "call here" << std::endl;
81+
#endif
82+
assert(n <= __size);
7583
start = __free_list_ptr;
84+
end = start; // debug 20240507 miss this
7685
for (size_t i = 0; i < n - 1; i++)
7786
end = free_list::__next_obj(end);
7887
__free_list_ptr = free_list::__next_obj(end);
@@ -178,6 +187,7 @@ class span {
178187
span* __prev = nullptr;
179188
size_t __use_count = 0; // 切成段小块内存,被分配给threadCache的计数器
180189
void* __free_list = nullptr; // 切好的小块内存的自由链表
190+
bool __is_use = false; // 是否在被使用
181191
};
182192

183193
// 带头双向循环链表

include/log.hpp

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#define __YUFC_LOG__
55

66
#include <iostream>
7+
#include <map>
78
#include <string>
89

910
enum STATUES // 日志等级
@@ -14,12 +15,40 @@ enum STATUES // 日志等级
1415
ERROR,
1516
FATAL
1617
};
18+
19+
// 定义颜色代码
20+
#define RESET "\033[0m"
21+
#define RED "\033[1;31m" // 加粗红色
22+
#define GREEN "\033[1;32m" // 加粗绿色
23+
#define YELLOW "\033[1;33m" // 加粗黄色
24+
#define BLUE "\033[1;34m" // 加粗蓝色
25+
#define MAGENTA "\033[1;35m" // 加粗洋红
26+
27+
// 根据日志等级获取相应颜色
28+
inline const char* GetColor(const std::string& level) {
29+
std::map<std::string, int> m = { { "INFO", 0 }, { "DEBUG", 1 }, { "WARNING", 2 }, { "ERROR", 3 }, { "FATAL", 4 } };
30+
switch (m[level]) {
31+
case INFO:
32+
return BLUE;
33+
case DEBUG:
34+
return GREEN;
35+
case WARNING:
36+
return YELLOW;
37+
case ERROR:
38+
return MAGENTA;
39+
case FATAL:
40+
return RED;
41+
default:
42+
return RESET;
43+
}
44+
}
45+
1746
// LOG() << "message"
1847
inline std::ostream& Log(const std::string& level, const std::string& file_name, int line) {
1948
// 添加日志等级
20-
std::string message = "[";
21-
message += level;
22-
message += "]";
49+
std::string levelTag = std::string("[") + level + "]";
50+
std::string coloredLevelTag = std::string(GetColor(level)) + levelTag + RESET;
51+
std::string message = coloredLevelTag;
2352
// 添加报错文件名称
2453
message += "[";
2554
message += file_name;

makefile

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
test.out: *.cc ./src/*.cc
1+
out: *.cc ./src/*.cc
2+
g++ -o $@ $^ -std=c++11 -lpthread
3+
debug: *.cc ./src/*.cc
24
g++ -o $@ $^ -std=c++11 -lpthread -DPROJECT_DEBUG
35
.PHONY:clean
46
clean:
5-
rm -f test.out
7+
rm -f out debug

out

170 KB
Binary file not shown.

src/central_cache.cc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ span* central_cache::get_non_empty_span(span_list& list, size_t size) {
5353
#endif
5454
page_cache::get_instance()->__page_mtx.lock();
5555
span* cur_span = page_cache::get_instance()->new_span(size_class::num_move_page(size));
56+
cur_span->__is_use = true; // 表示已经被使用
5657
page_cache::get_instance()->__page_mtx.unlock();
5758
#ifdef PROJECT_DEBUG
5859
LOG(DEBUG) << "central_cache::get_non_empty_span() get new span success" << std::endl;
@@ -96,7 +97,13 @@ void central_cache::release_list_to_spans(void* start, size_t size) {
9697
// 说明这个span切分出去的所有小块都回来了
9798
// 归还给pagecache
9899
// 1. 把这一页从cc的这个桶的spanlist中拿掉
100+
// #ifdef PROJECT_DEBUG
101+
// LOG(DEBUG) << "***" << std::endl;
102+
// #endif
99103
__span_lists[index].erase(cur_span); // 从桶里面拿走
104+
// #ifdef PROJECT_DEBUG
105+
// LOG(DEBUG) << "after ***" << std::endl;
106+
// #endif
100107
// 2. 此时不用管这个span的freelist了,因为这些内存本来就是span初始地址后面的,然后顺序也是乱的,直接置空即可
101108
// (这里还不太理解)
102109
cur_span->__free_list = nullptr;

src/page_cache.cc

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,27 @@ page_cache page_cache::__s_inst;
88
span* page_cache::new_span(size_t k) {
99
assert(k > 0 && k < PAGES_NUM);
1010
// 先检查第k个桶是否有span
11+
// #ifdef PROJECT_DEBUG
12+
// LOG(DEBUG) << "before ***" << std::endl;
13+
// #endif
1114
if (!__span_lists[k].empty())
12-
return __span_lists->pop_front();
15+
return __span_lists[k].pop_front(); // ? __span_lists->pop_front();
16+
// #ifdef PROJECT_DEBUG
17+
// LOG(DEBUG) << "after ***" << std::endl;
18+
// #endif
1319
// 第k个桶是空的->去检查后面的桶里面有无span,如果有,可以把它进行切分
1420
for (size_t i = k + 1; i < PAGES_NUM; i++) {
1521
if (!__span_lists[i].empty()) {
16-
// 可以开始切了
17-
// 假设这个页是n页的,需要的是k页的
18-
// 1. 从__span_lists中拿下来 2. 切开 3. 一个返回给cc 4. 另一个挂到 n-k 号桶里面去
22+
// 可以开始切了
23+
// 假设这个页是n页的,需要的是k页的
24+
// 1. 从__span_lists中拿下来 2. 切开 3. 一个返回给cc 4. 另一个挂到 n-k 号桶里面去
25+
#ifdef PROJECT_DEBUG
26+
LOG(DEBUG) << "before ***" << std::endl;
27+
#endif
1928
span* n_span = __span_lists[i].pop_front();
29+
#ifdef PROJECT_DEBUG
30+
LOG(DEBUG) << "after ***" << std::endl;
31+
#endif
2032
span* k_span = new span;
2133
// 在n_span头部切除k页下来
2234
k_span->__page_id = n_span->__page_id; // <1>
@@ -33,6 +45,9 @@ span* page_cache::new_span(size_t k) {
3345
*/
3446
// 剩下的挂到相应位置
3547
__span_lists[n_span->__n].push_front(n_span);
48+
// 存储n_span的首尾页号跟n_span的映射,方便pc回收内存时进行合并查找
49+
__id_span_map[n_span->__page_id] = n_span;
50+
__id_span_map[n_span->__page_id + n_span->__n - 1] = n_span;
3651
// 这里记录映射(简历id和span的映射,方便cc回收小块内存时,查找对应的span)
3752
for (PAGE_ID j = 0; j < k_span->__n; j++) {
3853
__id_span_map[k_span->__page_id + j] = k_span;
@@ -62,10 +77,50 @@ span* page_cache::map_obj_to_span(void* obj) {
6277
auto ret = __id_span_map.find(id);
6378
if (ret != __id_span_map.end())
6479
return ret->second;
65-
LOG(FATAL);
80+
LOG(FATAL) << std::endl;
6681
assert(false);
6782
return nullptr;
6883
}
6984

7085
void page_cache::release_span_to_page(span* s) {
86+
// 对span前后对页尝试进行合并,缓解内存碎片问题
87+
while (true) {
88+
PAGE_ID prev_id = s->__page_id - 1; // 前一块span的id一定是当前span的id-1
89+
// 拿到id如何找span: 之前写好的map能拿到吗?
90+
// 找到了,如果isuse是false,就能合并了(向前合并+向后合并)
91+
// 如果遇到了合并大小超过了128页了,也要停止了
92+
auto ret = __id_span_map.find(prev_id);
93+
if (ret == __id_span_map.end()) // 前面的页号没有了,不合并了
94+
break;
95+
span* prev_span = ret->second;
96+
if (prev_span->__is_use == true) // 前面相邻页的span在使用,不合并了
97+
break;
98+
if (prev_span->__n + s->__n > PAGES_NUM - 1) // 合并出超过128页的span没办法管理,不合并了
99+
break;
100+
s->__page_id = prev_span->__page_id;
101+
s->__n += prev_span->__n;
102+
__span_lists[prev_span->__n].erase(prev_span); // 防止野指针,删掉
103+
delete prev_span; // 删掉这个span
104+
} // 向前合并的逻辑 while end;
105+
while (true) {
106+
PAGE_ID next_id = s->__page_id + s->__n - 1; // 注意这里的页号是+n-1了
107+
auto ret = __id_span_map.find(next_id);
108+
if (ret == __id_span_map.end()) // 后面的页号没有了
109+
break;
110+
span* next_span = ret->second;
111+
if (next_span->__is_use == true) // 后面相邻页的span在使用,不合并了
112+
break;
113+
if (next_span->__n + s->__n > PAGES_NUM - 1) // 合并出超过128页的span没办法管理,不合并了
114+
break;
115+
s->__page_id; // 起始页号不用变了,因为是向后合并
116+
s->__n += next_span->__n;
117+
__span_lists[next_span->__n].erase(next_span); // 防止野指针,删掉
118+
delete next_span;
119+
}
120+
// 已经合并完成了,把东西挂起来
121+
__span_lists[s->__n].push_front(s);
122+
s->__is_use = false;
123+
// 处理一下映射,方便别人找到我
124+
__id_span_map[s->__page_id] = s;
125+
__id_span_map[s->__page_id + s->__n - 1] = s;
71126
}

src/thread_cache.cc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ void thread_cache::deallocate(void* ptr, size_t size) {
5858
__free_lists[index].push(ptr);
5959
// 当链表长度大于一次批量申请的内存的时候,就开始还一段list给cc
6060
if (__free_lists[index].size() >= __free_lists[index].max_size()) {
61+
#ifdef PROJECT_DEBUG
62+
LOG(DEBUG) << __free_lists[index].size() << ":" << __free_lists[index].max_size() << std::endl;
63+
LOG(DEBUG) << "call list_too_long" << std::endl;
64+
#endif
6165
list_too_long(__free_lists[index], size);
6266
}
6367
}
@@ -66,5 +70,8 @@ void thread_cache::list_too_long(free_list& list, size_t size) {
6670
void* start = nullptr;
6771
void* end = nullptr;
6872
list.pop(start, end, list.max_size());
73+
#ifdef PROJECT_DEBUG
74+
LOG(DEBUG) << "list pop success -> call release_list_to_spans()" << std::endl;
75+
#endif
6976
central_cache::get_instance()->release_list_to_spans(start, size);
7077
}

unit_test.cc

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
#include "./include/tcmalloc.hpp"
44
#include <iostream>
5+
#include <map>
6+
#include <random>
57
#include <thread>
8+
#include <functional>
69

710
void alloc1() {
811
for (size_t i = 0; i < 5; i++) {
@@ -41,7 +44,31 @@ void test_alloc2() {
4144
void* p2 = tcmalloc(6); // 这一次一定会找新的span
4245
}
4346

47+
void test_dealloc(int alloc_times = 10) {
48+
// 创建随机数生成器
49+
std::random_device rd;
50+
std::mt19937 gen(rd()); // 以随机设备作为种子
51+
std::uniform_int_distribution<> distrib(1, 127 * 1024); // 设置随机数分布范围1-127
52+
// 生成并输出随机数
53+
std::map<void*, size_t> s;
54+
for (int i = 0; i < alloc_times; i++) {
55+
int sz = distrib(gen);
56+
s.insert({ tcmalloc(sz), sz }); // 申请随机值
57+
}
58+
for (auto& e : s) {
59+
tcfree(e.first, e.second);
60+
}
61+
}
62+
63+
void test_multi_thread() {
64+
std::thread t1(std::bind(test_dealloc, 200));
65+
// std::thread t2(std::bind(test_dealloc, 20));
66+
t1.join();
67+
// t2.join();
68+
std::cout << "run successful" << std::endl;
69+
}
70+
4471
int main() {
45-
test_alloc2();
72+
test_multi_thread();
4673
return 0;
4774
}

0 commit comments

Comments
 (0)