|
17 | 17 | - [获取span详解](#获取span详解) |
18 | 18 | - [关于 new\_span 如何加锁的文字(重要/容易出bug)](#关于-new_span-如何加锁的文字重要容易出bug) |
19 | 19 | - [内存申请流程联调](#内存申请流程联调) |
| 20 | + - [thread\_cache内存释放](#thread_cache内存释放) |
| 21 | + - [central\_cache内存释放](#central_cache内存释放) |
| 22 | + - [page\_cache内存释放](#page_cache内存释放) |
| 23 | + - [大于256k的情况](#大于256k的情况) |
| 24 | + - [处理代码中`new`的问题](#处理代码中new的问题) |
20 | 25 |
|
21 | 26 | *** |
22 | 27 |
|
@@ -894,4 +899,301 @@ span* central_cache::get_non_empty_span(span_list& list, size_t size) { |
894 | 899 | } |
895 | 900 | ``` |
896 | 901 |
|
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`的,所以我们要改一下。 |
0 commit comments