Skip to content

Commit f3566e8

Browse files
committed
feat centralCache part, done
1 parent e6d7c22 commit f3566e8

File tree

12 files changed

+470
-32
lines changed

12 files changed

+470
-32
lines changed

README.md

Lines changed: 289 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
- [哈系桶的映射规则](#哈系桶的映射规则)
1111
- [threadCache的tls无锁访问](#threadcache的tls无锁访问)
1212
- [写tcfree的时候的一个遗留问题](#写tcfree的时候的一个遗留问题)
13+
- [central\_cache整体结构](#central_cache整体结构)
14+
- [central\_cache的核心逻辑](#central_cache的核心逻辑)
15+
- [central\_cache里面fetch\_range\_obj的逻辑](#central_cache里面fetch_range_obj的逻辑)
16+
- [page\_cache整体框架](#page_cache整体框架)
1317

1418
***
1519

@@ -305,4 +309,288 @@ void thread_cache::deallocate(void* ptr, size_t size) {
305309
306310
我这里是要传大小的,但是呢,p_tls_thread_cache->deallocate()需要给size,不然不知道还到哪一个桶上。但是我们的free是不用传size的,这里如何解决?
307311
308-
目前解决不了,先保留这个问题,留到后面再解决。
312+
目前解决不了,先保留这个问题,留到后面再解决。
313+
314+
## central_cache整体结构
315+
316+
centralCache也是一个哈希桶结构,他的哈希桶的映射关系跟threadCache是一样的。不同的是他的每个哈希桶位置挂是SpanList链表结构,不过每个映射桶下面的span中的大内存块被按映射关系切成了一个个小内存块对象挂在span的自由链表中。
317+
318+
这里是需要加锁的,但是是桶锁。如果不同线程获取不同的桶的东西,就不用加锁。
319+
320+
![](./assets/3.png)
321+
322+
**申请内存:**
323+
1. 当thread cache中没有内存时,就会批量向central cache申请一些内存对象,这里的批量获取对 象的数量使用了类似网络tcp协议拥塞控制的慢开始算法;central cache也有一个哈希映射的 spanlist,spanlist中挂着span,从span中取出对象给thread cache,这个过程是需要加锁的,不 过这里使用的是一个桶锁,尽可能提高效率。
324+
2. central cache映射的spanlist中所有span的都没有内存以后,则需要向page cache申请一个新的 span对象,拿到span以后将span管理的内存按大小切好作为自由链表链接到一起。然后从span 中取对象给thread cache。
325+
3. central cache的中挂的span中use_count记录分配了多少个对象出去,分配一个对象给thread cache,就++use_count
326+
327+
328+
**释放内存:**
329+
330+
1. 当threadCache过长或者线程销毁,则会将内存释放回centralCache中的,释放回来时--use_count。当use_count减到0时则表示所有对象都回到了span,则将span释放回pageCache,pageCache中会对前后相邻的空闲页进行合并。
331+
332+
**centralCache里面的小对象是由大对象切出来的,大对象就是Span。**
333+
334+
span的链表是双向链表。
335+
336+
span除了central_cache要用,后面的page_cache也要用,所以定义到common里面去吧。
337+
338+
然后这里存在一个问题,就是这个size_t,在64位下不够了,需要条件编译处理一下。
339+
340+
common.hpp
341+
```cpp
342+
#if defined(_WIN64) || defined(__x86_64__) || defined(__ppc64__) || defined(__aarch64__)
343+
typedef unsigned long long PAGE_ID;
344+
#else
345+
typedef size_t PAGE_ID;
346+
#endif
347+
```
348+
349+
这里如果在windows下有个坑,win64下是既有win64也有win32的定义的,所以要先判断64的,避免出bug。
350+
351+
```cpp
352+
// 管理大块内存
353+
class span {
354+
public:
355+
PAGE_ID __page_id; // 大块内存起始页的页号
356+
size_t __n; // 页的数量
357+
// 双向链表结构
358+
span* __next;
359+
span* __prev;
360+
size_t __use_count; // 切成段小块内存,被分配给threadCache的计数器
361+
void* __free_list; // 切好的小块内存的自由链表
362+
};
363+
```
364+
365+
然后就要手撕一个双链表了,十分简单,不多说了。这里面,每一个桶要维护一个锁!
366+
367+
common.hpp
368+
```cpp
369+
// 带头双向循环链表
370+
class span_list {
371+
private:
372+
span* __head = nullptr;
373+
std::mutex __bucket_mtx;
374+
public:
375+
span_list() {
376+
__head = new span;
377+
__head->__next = __head;
378+
__head->__prev = __head;
379+
}
380+
void insert(span* pos, span* new_span) {
381+
// 插入的是一个完好的span
382+
assert(pos);
383+
assert(new_span);
384+
span* prev = pos->__prev;
385+
prev->__next = new_span;
386+
new_span->__prev = prev;
387+
new_span->__next = pos;
388+
pos->__prev = new_span;
389+
}
390+
void erase(span* pos) {
391+
assert(pos);
392+
assert(pos != __head);
393+
span* prev = pos->__prev;
394+
span* next = pos->__next;
395+
prev->__next = next;
396+
next->__prev = prev;
397+
}
398+
};
399+
```
400+
401+
central_cache.hpp
402+
```cpp
403+
#include "../common.hpp"
404+
405+
class central_cache {
406+
private:
407+
span_list __span_lists[BUCKETS_NUM]; // 有多少个桶就多少个
408+
public:
409+
410+
};
411+
```
412+
413+
有多少个桶就有多少把锁!
414+
415+
416+
## central_cache的核心逻辑
417+
418+
**很明显这里是比较适合使用单例模式的。因为每个进程只需要维护一个central_cache。单例模式的详细说明可以见我的博客: [单例模式](https://blog.csdn.net/Yu_Cblog/article/details/131787131)**
419+
420+
421+
然后这里我们用饿汉模式。
422+
423+
```cpp
424+
class central_cache {
425+
private:
426+
span_list __span_lists[BUCKETS_NUM]; // 有多少个桶就多少个
427+
private:
428+
static central_cache __s_inst;
429+
central_cache() = default; // 构造函数私有
430+
central_cache(const central_cache&) = delete; // 不允许拷贝
431+
public:
432+
central_cache* get_instance() { return &__s_inst; }
433+
public:
434+
};
435+
```
436+
437+
438+
然后threadCache找你要内存了,你给多少呢?
439+
440+
这里用了一个类似tcp的慢开始的反馈算法。我们可以把这个算法写到size_class里面去。
441+
442+
common.hpp::size_class
443+
```cpp
444+
// 一次threadCache从centralCache获取多少个内存
445+
static inline size_t num_move_size(size_t size) {
446+
if (size == 0)
447+
return 0;
448+
// [2, 512], 一次批量移动多少个对象的(慢启动)上限制
449+
// 小对象一次批量上限高
450+
// 大对象一次批量上限低
451+
int num = MAX_BYTES / size;
452+
if (num < 2)
453+
num = 2;
454+
if (num > 512)
455+
num = 512;
456+
return num;
457+
}
458+
```
459+
460+
用这个方法,可以告诉threadCache,本次要从centralCache获取多少内存。
461+
462+
然后为了控制慢开始,在free_list里面还需要控制一个max_size,然后这个字段递增,就能控制慢启动了。
463+
464+
thread_cache.cc
465+
```cpp
466+
void* thread_cache::fetch_from_central_cache(size_t index, size_t size) {
467+
// 慢开始反馈调节算法
468+
size_t batch_num = std::min(__free_lists[index].max_size(), size_class::num_move_size(size));
469+
if (__free_lists[index].max_size() == batch_num)
470+
__free_lists[index].max_size() += 1; // 最多增长到512了
471+
// 1. 最开始一次向centralCache要太多,因为太多了可能用不完
472+
// 2. 如果你一直有这个桶size大小的内存,那么后面我可以给你越来越多,直到上限(size_class::num_move_size(size))
473+
// 这个上限是根据这个桶的内存块大小size来决定的
474+
// 3. size越大,一次向centralcache要的就越小,如果size越小,相反。
475+
return nullptr;
476+
}
477+
```
478+
479+
480+
然后去调用这个fetch_range_obj函数。
481+
482+
参数的意义:获取一段内存,从start到end个块,一共获取batch_num个,然后每一个块的大小是size,end-start应该等于batch_num。
483+
484+
返回值的意义:这里向central_cache中的span获取batch_num个,那么这个span一定有这么多个吗?不一定。span下如果不够,就全部给你。actual_n表示实际获取到了多少个。 1 <= actual_n <= batch_num。
485+
486+
487+
488+
thread_cache.cc
489+
```cpp
490+
void* thread_cache::fetch_from_central_cache(size_t index, size_t size) {
491+
// 慢开始反馈调节算法
492+
size_t batch_num = std::min(__free_lists[index].max_size(), size_class::num_move_size(size));
493+
if (__free_lists[index].max_size() == batch_num)
494+
__free_lists[index].max_size() += 1; // 最多增长到512了
495+
// 1. 最开始一次向centralCache要太多,因为太多了可能用不完
496+
// 2. 如果你一直有这个桶size大小的内存,那么后面我可以给你越来越多,直到上限(size_class::num_move_size(size))
497+
// 这个上限是根据这个桶的内存块大小size来决定的
498+
// 3. size越大,一次向centralcache要的就越小,如果size越小,相反。
499+
500+
// 开始获取内存了
501+
void* start = nullptr;
502+
void* end = nullptr;
503+
size_t actual_n = central_cache::get_instance()->fetch_range_obj(start, end, batch_num, size);
504+
return nullptr;
505+
}
506+
```
507+
508+
然后我们获取到从cc(centralCache)里面的内存了,这里分两种情况:
509+
510+
1. cc只给了tc一个内存块(actual_n==1时), 此时直接返回就行了。此时thread_cache::allocate会直接把获取到的这一块交给用户,不用经过tc的哈希桶了。
511+
2. 但是如果cc给我们的是一段(actual_n>=1),只需要给用户其中一块,其他的要插入到tc里面去!所以我们要给free_list提供一个插入一段(好几块size大小内存)的方法,也是头插就行了。
512+
513+
可以重载一下。
514+
515+
common.hpp::free_list
516+
```cpp
517+
void push(void* obj) {
518+
assert(obj);
519+
__next_obj(obj) = __free_list_ptr;
520+
__free_list_ptr = obj;
521+
}
522+
void push(void* start, void* end) {
523+
__next_obj(end) = __free_list_ptr;
524+
__free_list_ptr = start;
525+
}
526+
```
527+
528+
thread_cache.cc
529+
```cpp
530+
if (actual_n == 1) {
531+
assert(start == end);
532+
return start;
533+
} else {
534+
__free_lists[index].push(free_list::__next_obj(start), end);
535+
return start;
536+
}
537+
538+
```
539+
540+
这里push的是start的下一个位置,start就不用经过tc了,start直接返回给用户,然后start+1到end位置的,插入到tc里面去。
541+
542+
## central_cache里面fetch_range_obj的逻辑
543+
544+
```cpp
545+
size_t central_cache::fetch_range_obj(void*& start, void*& end, size_t batch_num, size_t size) {
546+
size_t index = size_class::bucket_index(size); // 算出在哪个桶找
547+
548+
}
549+
```
550+
551+
算出在哪桶里面找之后,就要分情况了。
552+
553+
首先,如果这个桶里面一个span都没挂,那就要找下一层了,找pc要。
554+
555+
如果有挂一些span,也要分情况。
556+
557+
我们要先找到一个非空的span。
558+
559+
所以写一个方法,不过这个方法可以后面再实现。
560+
561+
然后这里要注意一下。自由链表是单链表,如果我们取一段出来,最后要记得链表末尾给一个nullptr。
562+
563+
**注意细节:**
564+
1. 取batch_num个,end指针只需要走batch_num步(前提是span下面够这么多)!
565+
2. 如果span下面不够,要特殊处理!
566+
567+
568+
```cpp
569+
size_t central_cache::fetch_range_obj(void*& start, void*& end, size_t batch_num, size_t size) {
570+
size_t index = size_class::bucket_index(size); // 算出在哪个桶找
571+
__span_lists[index].__bucket_mtx.lock(); // 加锁(可以考虑RAII)
572+
span* cur_span = get_non_empty_span(__span_lists[index], size); // 找一个非空的span(有可能找不到)
573+
assert(cur_span);
574+
assert(cur_span->__free_list); // 这个非空的span一定下面挂着内存了,所以断言一下
575+
576+
start = cur_span->__free_list;
577+
// 这里要画图理解一下
578+
end = start;
579+
// 开始指针遍历,从span中获取对象,如果不够,有多少拿多少
580+
size_t i = 0;
581+
size_t actual_n = 1;
582+
while (i < batch_num - 1 && free_list::__next_obj(end) != nullptr) {
583+
end = free_list::__next_obj(end);
584+
++i;
585+
++actual_n;
586+
}
587+
cur_span->__free_list = free_list::__next_obj(end);
588+
free_list::__next_obj(end) = nullptr;
589+
__span_lists[index].__bucket_mtx.unlock(); // 解锁
590+
return actual_n;
591+
}
592+
```
593+
594+
当然cc到这里还没有完全写完的,但是我们要继续先写pc,才能来完善这里的部分。
595+
596+
## page_cache整体框架

assets/3.png

269 KB
Loading

central_cache/central_cache.cc

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
2+
#include "central_cache.hpp"
3+
4+
central_cache central_cache::__s_inst;
5+
6+
size_t central_cache::fetch_range_obj(void*& start, void*& end, size_t batch_num, size_t size) {
7+
size_t index = size_class::bucket_index(size); // 算出在哪个桶找
8+
__span_lists[index].__bucket_mtx.lock(); // 加锁(可以考虑RAII)
9+
span* cur_span = get_non_empty_span(__span_lists[index], size); // 找一个非空的span(有可能找不到)
10+
assert(cur_span);
11+
assert(cur_span->__free_list); // 这个非空的span一定下面挂着内存了,所以断言一下
12+
13+
start = cur_span->__free_list;
14+
// 这里要画图理解一下
15+
end = start;
16+
// 开始指针遍历,从span中获取对象,如果不够,有多少拿多少
17+
size_t i = 0;
18+
size_t actual_n = 1;
19+
while (i < batch_num - 1 && free_list::__next_obj(end) != nullptr) {
20+
end = free_list::__next_obj(end);
21+
++i;
22+
++actual_n;
23+
}
24+
cur_span->__free_list = free_list::__next_obj(end);
25+
free_list::__next_obj(end) = nullptr;
26+
27+
__span_lists[index].__bucket_mtx.unlock(); // 解锁
28+
return actual_n;
29+
}
30+
31+
span* central_cache::get_non_empty_span(span_list& list, size_t size) {
32+
return nullptr;
33+
}

central_cache/central_cache.hpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
2+
#ifndef __YUFC_CENTRAL_CACHE_HPP__
3+
#define __YUFC_CENTRAL_CACHE_HPP__
4+
5+
#include "../common.hpp"
6+
7+
class central_cache {
8+
private:
9+
span_list __span_lists[BUCKETS_NUM]; // 有多少个桶就多少个
10+
private:
11+
static central_cache __s_inst;
12+
central_cache() = default; // 构造函数私有
13+
central_cache(const central_cache&) = delete; // 不允许拷贝
14+
public:
15+
static central_cache* get_instance() { return &__s_inst; }
16+
size_t fetch_range_obj(void*& start, void*& end, size_t batch_num, size_t size);
17+
span* get_non_empty_span(span_list& list, size_t size);
18+
public:
19+
};
20+
21+
#endif

0 commit comments

Comments
 (0)