Skip to content

Commit 0feae7d

Browse files
authored
Merge pull request #5 from ffengc/dev
dev to main: The tcmalloc project is basically completed
2 parents 206add9 + 38caa52 commit 0feae7d

File tree

15 files changed

+1965
-52
lines changed

15 files changed

+1965
-52
lines changed

README.md

Lines changed: 303 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
1717
- [获取span详解](#获取span详解)
1818
- [关于 new\_span 如何加锁的文字(重要/容易出bug)](#关于-new_span-如何加锁的文字重要容易出bug)
1919
- [内存申请流程联调](#内存申请流程联调)
20+
- [thread\_cache内存释放](#thread_cache内存释放)
21+
- [central\_cache内存释放](#central_cache内存释放)
22+
- [page\_cache内存释放](#page_cache内存释放)
23+
- [大于256k的情况](#大于256k的情况)
24+
- [处理代码中`new`的问题](#处理代码中new的问题)
2025

2126
***
2227

@@ -894,4 +899,301 @@ span* central_cache::get_non_empty_span(span_list& list, size_t size) {
894899
}
895900
```
896901

897-
## 内存申请流程联调
902+
## 内存申请流程联调
903+
904+
先给每一步打上日志,看看调用的流程。
905+
906+
然后多次调用tcmalloc,看看日志。
907+
908+
unit_test.cc
909+
```cpp
910+
void test_alloc() {
911+
std::cout << "call tcmalloc(1)" << std::endl;
912+
void* ptr = tcmalloc(8 * 1024);
913+
std::cout << "call tcmalloc(2)" << std::endl;
914+
ptr = tcmalloc(10);
915+
std::cout << "call tcmalloc(3)" << std::endl;
916+
ptr = tcmalloc(2);
917+
std::cout << "call tcmalloc(4)" << std::endl;
918+
ptr = tcmalloc(1);
919+
std::cout << "call tcmalloc(5)" << std::endl;
920+
ptr = tcmalloc(1);
921+
std::cout << "call tcmalloc(6)" << std::endl;
922+
ptr = tcmalloc(5);
923+
std::cout << "call tcmalloc(7)" << std::endl;
924+
ptr = tcmalloc(1);
925+
}
926+
```
927+
928+
输出日志:
929+
```bash
930+
call tcmalloc(1)
931+
[DEBUG][./include/tcmalloc.hpp][14] tcmalloc find tc from mem
932+
[DEBUG][src/thread_cache.cc][16] thread_cache::allocate call thread_cache::fetch_from_central_cache
933+
[DEBUG][src/thread_cache.cc][43] thread_cache::fetch_from_central_cache call central_cache::get_instance()->fetch_range_obj()
934+
[DEBUG][src/central_cache.cc][12] central_cache::fetch_range_obj() call central_cache::get_non_empty_span()
935+
[DEBUG][src/central_cache.cc][45] central_cache::get_non_empty_span() cannot find non-null span in cc, goto pc for mem
936+
[DEBUG][src/central_cache.cc][52] central_cache::get_non_empty_span() call page_cache::get_instance()->new_span()
937+
[DEBUG][src/page_cache.cc][43] page_cache::new_span() cannot find span, goto os for mem
938+
[DEBUG][src/page_cache.cc][37] page_cache::new_span() have span, return
939+
[DEBUG][src/central_cache.cc][58] central_cache::get_non_empty_span() get new span success
940+
[DEBUG][src/central_cache.cc][70] central_cache::get_non_empty_span() cut span
941+
[DEBUG][src/thread_cache.cc][47] actual_n:1
942+
call tcmalloc(2)
943+
[DEBUG][./include/tcmalloc.hpp][14] tcmalloc find tc from mem
944+
[DEBUG][src/thread_cache.cc][16] thread_cache::allocate call thread_cache::fetch_from_central_cache
945+
[DEBUG][src/thread_cache.cc][43] thread_cache::fetch_from_central_cache call central_cache::get_instance()->fetch_range_obj()
946+
[DEBUG][src/central_cache.cc][12] central_cache::fetch_range_obj() call central_cache::get_non_empty_span()
947+
[DEBUG][src/central_cache.cc][45] central_cache::get_non_empty_span() cannot find non-null span in cc, goto pc for mem
948+
[DEBUG][src/central_cache.cc][52] central_cache::get_non_empty_span() call page_cache::get_instance()->new_span()
949+
[DEBUG][src/page_cache.cc][37] page_cache::new_span() have span, return
950+
[DEBUG][src/central_cache.cc][58] central_cache::get_non_empty_span() get new span success
951+
[DEBUG][src/central_cache.cc][70] central_cache::get_non_empty_span() cut span
952+
[DEBUG][src/thread_cache.cc][47] actual_n:1
953+
call tcmalloc(3)
954+
[DEBUG][./include/tcmalloc.hpp][14] tcmalloc find tc from mem
955+
[DEBUG][src/thread_cache.cc][16] thread_cache::allocate call thread_cache::fetch_from_central_cache
956+
[DEBUG][src/thread_cache.cc][43] thread_cache::fetch_from_central_cache call central_cache::get_instance()->fetch_range_obj()
957+
[DEBUG][src/central_cache.cc][12] central_cache::fetch_range_obj() call central_cache::get_non_empty_span()
958+
[DEBUG][src/central_cache.cc][45] central_cache::get_non_empty_span() cannot find non-null span in cc, goto pc for mem
959+
[DEBUG][src/central_cache.cc][52] central_cache::get_non_empty_span() call page_cache::get_instance()->new_span()
960+
[DEBUG][src/page_cache.cc][37] page_cache::new_span() have span, return
961+
[DEBUG][src/central_cache.cc][58] central_cache::get_non_empty_span() get new span success
962+
[DEBUG][src/central_cache.cc][70] central_cache::get_non_empty_span() cut span
963+
[DEBUG][src/thread_cache.cc][47] actual_n:1
964+
call tcmalloc(4)
965+
[DEBUG][./include/tcmalloc.hpp][14] tcmalloc find tc from mem
966+
[DEBUG][src/thread_cache.cc][16] thread_cache::allocate call thread_cache::fetch_from_central_cache
967+
[DEBUG][src/thread_cache.cc][43] thread_cache::fetch_from_central_cache call central_cache::get_instance()->fetch_range_obj()
968+
[DEBUG][src/central_cache.cc][12] central_cache::fetch_range_obj() call central_cache::get_non_empty_span()
969+
[DEBUG][src/thread_cache.cc][47] actual_n:2
970+
call tcmalloc(5)
971+
[DEBUG][./include/tcmalloc.hpp][14] tcmalloc find tc from mem
972+
call tcmalloc(6)
973+
[DEBUG][./include/tcmalloc.hpp][14] tcmalloc find tc from mem
974+
[DEBUG][src/thread_cache.cc][16] thread_cache::allocate call thread_cache::fetch_from_central_cache
975+
[DEBUG][src/thread_cache.cc][43] thread_cache::fetch_from_central_cache call central_cache::get_instance()->fetch_range_obj()
976+
[DEBUG][src/central_cache.cc][12] central_cache::fetch_range_obj() call central_cache::get_non_empty_span()
977+
[DEBUG][src/thread_cache.cc][47] actual_n:3
978+
call tcmalloc(7)
979+
[DEBUG][./include/tcmalloc.hpp][14] tcmalloc find tc from mem
980+
```
981+
982+
983+
同样,再测一次。
984+
985+
```cpp
986+
void test_alloc2() {
987+
for (size_t i = 0; i < 1024; ++i) {
988+
void* p1 = tcmalloc(6);
989+
}
990+
void* p2 = tcmalloc(6); // 这一次一定会找新的span
991+
}
992+
```
993+
994+
如果申请1024次6字节(对齐后为8字节),第1025次申请,一定会向系统申请新的span了,之前都不需要的!所以预期输出只有两个`goto os for mem`
995+
996+
输出日志放在了 `./test/test1.log`
997+
998+
999+
## thread_cache内存释放
1000+
1001+
当链表长度大于一次批量申请的内存的时候,就开始还一段list给cc
1002+
1003+
thread_cache.cc
1004+
```cpp
1005+
void thread_cache::deallocate(void* ptr, size_t size) {
1006+
assert(ptr);
1007+
assert(size <= MAX_BYTES);
1008+
size_t index = size_class::bucket_index(size);
1009+
__free_lists[index].push(ptr);
1010+
// 当链表长度大于一次批量申请的内存的时候,就开始还一段list给cc
1011+
if (__free_lists[index].size() >= __free_lists[index].max_size()) {
1012+
list_too_long(__free_lists[index], size);
1013+
}
1014+
}
1015+
```
1016+
1017+
thread_cache.cc
1018+
```cpp
1019+
void thread_cache::list_too_long(free_list& list, size_t size) {
1020+
void* start = nullptr;
1021+
void* end = nullptr;
1022+
list.pop(start, end, list.max_size());
1023+
central_cache::get_instance()->release_list_to_spans(start, size);
1024+
}
1025+
```
1026+
1027+
tcmalloc的规则更复杂,可能还会控制内存大小,超过...就会释放等。
1028+
1029+
1030+
## central_cache内存释放
1031+
1032+
```cpp
1033+
void central_cache::release_list_to_spans(void* start, size_t size) {
1034+
size_t index = size_class::bucket_index(size); // 先算一下在哪一个桶里面
1035+
__span_lists[index].__bucket_mtx.lock();
1036+
// 这里要注意,一个桶挂了多个span,这些内存块挂到哪一个span是不确定的
1037+
1038+
__span_lists[index].__bucket_mtx.unlock();
1039+
}
1040+
```
1041+
1042+
**这里的问题是:如何确定每一块内存块应该到哪一个span里面去。**
1043+
1044+
1045+
现在要判断,这些内存块,是来自哪个span的,然后span是从page切出来的,page是有地址的,span也是有地址的。
1046+
1047+
所以最好在page里面的时候,先让pageid和span的地址映射起来先。
1048+
1049+
在pc.hpp里面增加。
1050+
```cpp
1051+
std::unordered_map<PAGE_ID, span*> __id_span_map;
1052+
```
1053+
1054+
**然后在new_span里面,把新的span分给cc的时候,记录一下映射。**
1055+
1056+
然后pc里面提供一个方法,获取对象到span映射。
1057+
1058+
page_cache.cc
1059+
```cpp
1060+
span* page_cache::map_obj_to_span(void* obj) {
1061+
// 先把页号算出来
1062+
PAGE_ID id = (PAGE_ID)obj >> PAGE_SHIFT; // 这个理论推导可以自行推导一下
1063+
auto ret = __id_span_map.find(id);
1064+
if (ret != __id_span_map.end())
1065+
return ret->second;
1066+
LOG(FATAL);
1067+
assert(false);
1068+
return nullptr;
1069+
}
1070+
```
1071+
1072+
此时就可以通过一个对象,获取到对应是哪一个span了。
1073+
1074+
此时就可以继续写`release_list_to_spans`了。
1075+
1076+
```cpp
1077+
void central_cache::release_list_to_spans(void* start, size_t size) {
1078+
size_t index = size_class::bucket_index(size); // 先算一下在哪一个桶里面
1079+
__span_lists[index].__bucket_mtx.lock();
1080+
// 这里要注意,一个桶挂了多个span,这些内存块挂到哪一个span是不确定的
1081+
while (start) {
1082+
// 遍历这个链表
1083+
void* next = free_list::__next_obj(start); // 先记录一下下一个,避免等下找不到了
1084+
span* cur_span = page_cache::get_instance()->map_obj_to_span(start);
1085+
free_list::__next_obj(start) = cur_span->__free_list;
1086+
cur_span->__free_list = start;
1087+
// 处理usecount
1088+
cur_span->__use_count--;
1089+
if (cur_span->__use_count == 0) {
1090+
// 说明这个span切分出去的所有小块都回来了
1091+
// 归还给pagecache
1092+
// 1. 把这一页从cc的这个桶的spanlist中拿掉
1093+
__span_lists[index].erase(cur_span); // 从桶里面拿走
1094+
// 2. 此时不用管这个span的freelist了,因为这些内存本来就是span初始地址后面的,然后顺序也是乱的,直接置空即可
1095+
// (这里还不太理解)
1096+
cur_span->__free_list = nullptr;
1097+
cur_span->__next = cur_span->__prev = nullptr;
1098+
// 页号,页数是不能动的!
1099+
// 3. 解开桶锁
1100+
__span_lists[index].__bucket_mtx.unlock();
1101+
// 4. 还给pc
1102+
page_cache::get_instance()->__page_mtx.lock();
1103+
page_cache::get_instance()->release_span_to_page(cur_span);
1104+
page_cache::get_instance()->__page_mtx.unlock();
1105+
// 5. 恢复桶锁
1106+
__span_lists[index].__bucket_mtx.lock();
1107+
}
1108+
start = next;
1109+
}
1110+
__span_lists[index].__bucket_mtx.unlock();
1111+
}
1112+
```
1113+
1114+
细节在注释里面写的很清楚了。
1115+
1116+
要注意,调用pc的接口的时候,就记得把桶锁解掉。
1117+
1118+
## page_cache内存释放
1119+
1120+
就是这个函数。
1121+
1122+
```cpp
1123+
void page_cache::release_span_to_page(span* s) {
1124+
// 对span前后对页尝试进行合并,缓解内存碎片问题
1125+
}
1126+
```
1127+
1128+
然后刚才的map可以帮助我们查找前后的page。
1129+
1130+
然后我们前后找的时候,要区分这个页是不是在centralCache上的,如果在cc上,那就不能合并。
1131+
1132+
然后这个判断不能用use_count==0这个判断条件。有可能这个span刚从pc拿过来,还没给别人的时候,use_count就是0,这个span,pc是不能回收合并的。
1133+
1134+
所以可以给span添加一个参数is_use就行了。
1135+
1136+
```cpp
1137+
// 管理大块内存
1138+
class span {
1139+
public:
1140+
PAGE_ID __page_id; // 大块内存起始页的页号
1141+
size_t __n = 0; // 页的数量
1142+
// 双向链表结构
1143+
span* __next = nullptr;
1144+
span* __prev = nullptr;
1145+
size_t __use_count = 0; // 切成段小块内存,被分配给threadCache的计数器
1146+
void* __free_list = nullptr; // 切好的小块内存的自由链表
1147+
bool is_use = false; // 是否在被使用
1148+
};
1149+
```
1150+
1151+
然后cc.cc这里改一下,拿到之后改成true就行。
1152+
1153+
cc.cc
1154+
```cpp
1155+
page_cache::get_instance()->__page_mtx.lock();
1156+
span* cur_span = page_cache::get_instance()->new_span(size_class::num_move_page(size));
1157+
cur_span->is_use = true; // 表示已经被使用
1158+
page_cache::get_instance()->__page_mtx.unlock();
1159+
```
1160+
1161+
1162+
然后继续写这个逻辑:
1163+
1164+
page_cache.cc
1165+
```cpp
1166+
void page_cache::release_span_to_page(span* s) {
1167+
// 对span前后对页尝试进行合并,缓解内存碎片问题
1168+
PAGE_ID prev_id = s->__page_id - 1; // 前一块span的id一定是当前span的id-1
1169+
// 拿到id如何找span: 之前写好的map能拿到吗?
1170+
}
1171+
```
1172+
1173+
现在的问题是,之前的map能拿到吗?还拿不到,因为我们之前的map只记录了分给cc的span的映射,没有存留在pc那些,没有分出去的映射。
1174+
所以我们要在`span* page_cache::new_span(size_t k) {`里面添加一下,留在pagecache那些块的映射。
1175+
1176+
```cpp
1177+
// 存储n_span的首尾页号跟n_span的映射,方便pc回收内存时进行合并查找
1178+
__id_span_map[n_span->__page_id] = n_span;
1179+
__id_span_map[n_span->__page_id + n_span->__n - 1] = n_span;
1180+
```
1181+
1182+
为什么这里不用循环存储呢?
1183+
1184+
因为这里的pc的内存只是被span挂起来啊,不会被切啊,所以知道地址就了啊!
1185+
给cc的那些,会被切开变成很多固定大小的内存块啊!所以这里不用循环存。
1186+
1187+
## 大于256k的情况
1188+
1189+
1. <=256kb -> 按照前面三层缓存的情况进行操作
1190+
2. \>256kb的情况
1191+
a. 128\*8k > size > 32\*8k这个情况: 还可以找pagecache
1192+
b. 否则直接找系统
1193+
1194+
然后这一部分就是有多处要改,不过都很简单很容易找到,大家可以直接看代码。处理完之后,测试一下申请大内存就行。
1195+
1196+
1197+
## 处理代码中`new`的问题
1198+
1199+
代码中有些地方用了`new span`。这个就很不对。我们弄这个tcmalloc是用来替代malloc的,既然是替代,那我们的代码里面怎么能有`new``new`也是调用`malloc`的,所以我们要改一下。

include/central_cache.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,15 @@ class central_cache {
1313
central_cache(const central_cache&) = delete; // 不允许拷贝
1414
public:
1515
static central_cache* get_instance() { return &__s_inst; }
16+
// 将中心缓存获取一定数量的对象给threadCache
1617
size_t fetch_range_obj(void*& start, void*& end, size_t batch_num, size_t size);
18+
// 获取一个非空的span
1719
span* get_non_empty_span(span_list& list, size_t size);
20+
21+
public:
22+
// 将一定数量的对象释放到span中
23+
void release_list_to_spans(void* start, size_t byte_size);
24+
1825
public:
1926
};
2027

0 commit comments

Comments
 (0)