|
10 | 10 | - [哈系桶的映射规则](#哈系桶的映射规则) |
11 | 11 | - [threadCache的tls无锁访问](#threadcache的tls无锁访问) |
12 | 12 | - [写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整体框架) |
13 | 17 |
|
14 | 18 | *** |
15 | 19 |
|
@@ -305,4 +309,288 @@ void thread_cache::deallocate(void* ptr, size_t size) { |
305 | 309 |
|
306 | 310 | 我这里是要传大小的,但是呢,p_tls_thread_cache->deallocate()需要给size,不然不知道还到哪一个桶上。但是我们的free是不用传size的,这里如何解决? |
307 | 311 |
|
308 | | -目前解决不了,先保留这个问题,留到后面再解决。 |
| 312 | +目前解决不了,先保留这个问题,留到后面再解决。 |
| 313 | +
|
| 314 | +## central_cache整体结构 |
| 315 | +
|
| 316 | +centralCache也是一个哈希桶结构,他的哈希桶的映射关系跟threadCache是一样的。不同的是他的每个哈希桶位置挂是SpanList链表结构,不过每个映射桶下面的span中的大内存块被按映射关系切成了一个个小内存块对象挂在span的自由链表中。 |
| 317 | +
|
| 318 | +这里是需要加锁的,但是是桶锁。如果不同线程获取不同的桶的东西,就不用加锁。 |
| 319 | +
|
| 320 | + |
| 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整体框架 |
0 commit comments