当前位置: 首页 > news >正文

page cache设计及实现

在这里插入图片描述

你好,我是安然无虞。

page cache的设计及实现

page cache 本质上也是一个哈希桶, 它是按照页的数量进行映射的. 当 central cache 向 page cache 申请内存时, page cache 先检查对应位置是否有span, 如果没有则向更大页去寻找一个span, 如果找到则分裂成两个. 比如: 你申请7page的内存, 7页后面没有挂span, 则向后面寻找更大的span, 假设在10页位置找到了span, 则将10页的span分裂成7页的span和3页的span.

如果找到 _spanLists[128] 都没有合适的span, 则向系统堆申请128页的span挂到 _spanLists[128] 上.

可能大家会疑惑, 为什么是128, 而不是别的数字?
这个问题也就等同于为什么 page cache 中最大挂128页的span? 因为在这个项目里我们规定线程申请单个对象最大是256KB, 而128页可以被切成4个256KB的对象, 因此是足够的.
当然了, 具体情况具体分析, 在别的特殊场景下也可以自行设置.

//page cache 中哈希桶的个数
static const size_t NPAGES = 129;

为了让哈希桶的下标和页数对应起来, 我们将哈希桶的个数设置成129个, 也就是将0号下标舍弃.
在这里插入图片描述


page cache与central cache的不同:

我们知道 central cache 和 page cache 的核心结构都是 SpanList 的哈希桶, 但是它们的本质是有区别的, central cache 的哈希桶跟 thread cache 一样, 都是按照大小对齐关系映射的, 它的 SpanList 中挂的 span 中的内存都被按映射关系切好链接成小块内存的自由链表, 而 page cache 中的 SpanList 则是按照下标桶号映射的, 也就意味着第 i 号桶下面挂的span都是 i 页内存.

还有一点就是 central cache 中的多个桶可能会同时向 page cache 申请内存, 所以 page cache 是存在线程安全问题的, 因此在访问 page cache 时必须要加锁. 但是在page cache 这里我们不能像 central cache 一样使用桶锁, 因为当 central cache 向 page cache 申请内存时, page cache 可能会将其他桶当中大页span切小, 也就是说会访问到其他桶. 而且, 当central cache 将某个span归还给 page cache 时, page cache 也会尝试将该span与其他桶当中的span进行合并.

总之就是, 在访问 page cache 时, 我们可能需要访问 page cache 中的多个桶, 如果 page cache 用桶锁就会出现大量频繁的加锁和解锁操作, 导致程序的整体效率低下. 因此我们在访问 page cache 时用一把大锁将整个 page cache 给锁住.

而 thread cache 在访问 central cache 时, 也可能出现多个 thread cache 同时访问 central cache 的情况, 但是我们的 central cache 使用的是桶锁, 因为 central cache 的每个哈希桶中的span都被切分成了对应大小的内存对象并且通过自由链表链接起来了, thread cache 只需要根据自己所需对象的大小访问 central cache 中对应的哈希桶即可, 不会访问其他哈希桶, 因此 central cache 可以用桶锁.


page cache的实现方式

page cache在整个进程中也是只能存在一个的, 因此我们也需要将其设置为和 central cache 一样的单例模式.

// page cache本质是一个按页数映射的哈希桶
class PageCache
{
public:// 提供一个全局访问点static PageCache* GetInstance(){return &_sInst;}// 获取一个k页的spanSpan* NewSpan(size_t k);private:// 构造和拷贝构造设置为私有PageCache(){}PageCache(const PageCache&) = delete;static PageCache _sInst;private:SpanList _spanLists[NPAGES];public:std::mutex _pageMtx; // 一把大锁
};

程序运行起来直接创建出该对象:

PageCache PageCache::_sInst;

page cache中获取span

获取一个非空的span

我们知道 thread cache 在向 central cache 申请内存对象时, central cache 要先从对应的哈希桶中获取一个非空的span, 然后从这个span中取出若干个内存对象给 thread cache, 试问如何获取到这个非空的span呢?

首先遍历当前位置映射的双向链表, 如果该双向链表中有非空的span, 直接返回即可. 说到遍历, 需要在SpanList的定义当中给出Begin成员函数和End成员函数, 方便我们像使用迭代器一样遍历双向链表.

// 双向链表结构
class SpanList
{
public:SpanList(){_head = new Span;_head->_prev = _head;_head->_next = _head;}// 第一个spanSpan* Begin(){return _head->_next;}// 最后一个span的下一个位置Span* End(){return _head;}private:Span* _head = nullptr;
};

如果当前位置映射的SpanList中没有非空的span, 那么只能向 page cache 中申请大块内存了.

但是需要从 page cache 中获取多大的内存呢? 这个由具体对象的大小决定, 我们可以先根据对象的大小计算出 thread cache 一次向 central cache 获取对象数量的上限值, 然后将这个上限值乘以单个对象的大小, 计算出具体需要多少字节, 最后再将这个算出来的字节数转换为页数, 如果转换后不够一页, 那么我们就申请一页, 否则转换出来是几页就申请几页. 也就是说, central cache 向 page cache 申请内存时, 要求申请到的内存尽量能够满足 thread cache 向 central cache 申请时的上限.

// 管理对齐和映射的关系
class SizeClass
{
public:// 一次向page cache要多少页static size_t NumMovePage(size_t size){size_t num = NumMoveSize(size); // thread  cache一次向central cache获取对象的上限值size_t npage = num * size; //num个size大小的对象所需的字节数npage >>= PAGE_SHIFT; // 将字节数转化成页数if (npage == 0)npage = 1; // 最少给一页return npage;}
};

其中 PAGE_SHIFT 表示页大小转换偏移, 这里我们按每页8K的大小为例, 那么PAGE_SHIFT 的大小就是13.

// 页大小转换偏移,即一页定义为2^13Byte,也就是8KB
static const size_t PAGE_SHIFT = 13;

当 central cache 申请到了这个若干页的span之后, 还需要将其切成一个个所需要的内存对象链接起来, 然后挂到span的自由链表当中.

那怎么对这个大块span进行切分呢? 首先根据大块内存起始页的页号计算出大块内存的起始地址, 再根据大块内存的页数计算出大块内存的大小, 然后利用起始地址和大块内存的大小计算出大块内存的结束地址.

有了大块内存的起始地址和结束地址, 那么对其进行切分就简单多了, 但是有一点需要注意的是为什么我们这里选用的是尾插, 而不是头插呢?

因为将切好的对象尾插到自由链表, 这些对象看起来是按照链式结构链接起来的, 而实际它们在物理上是连续的, 这时当我们把这些连续内存分配给某个线程使用时, 可以提高该线程的CPU缓存利用率, 这样一来CPU高速缓存命中率会更高.

补充: CPU高速缓存命中率知识点

在这里插入图片描述
一般程序编译连接后, 生成可执行程序, CPU执行这个程序时需要去访问内存, 但是CPU太快了, 比内存快得多, 于是CPU总是在等内存.

所以CPU不会直接去访问内存, 因为它嫌内存的速度太慢了, 所以CPU会把数据加载到三级缓存或者寄存器中, 一般小数据加载到寄存器中, 大数据加载到缓存中.

那什么叫做命中呢? CPU会看数据是否在缓存, 在就叫命中, 可以直接访问, 不在就叫不命中, 此时需要先把数据从内存加载到缓存中, 之后再访问.

所以针对CPU高速缓存命中率这个知识点, 我们可以用之前学习的顺序表和链表来举例子:
首先我们知道顺序表在物理空间上是连续的, 而链表在物理空间上是不连续的.
在这里插入图片描述
当我们访问顺序表第一个元素时, 会看其是否在缓存中, 不在, 不命中, 然后就把后面的一段加载到缓存中去(具体加载多少, 看硬件), 然后再访问后面的元素就都命中了.

如果是链表, 虽然也会将后面的一段数据加载到缓存中, 但是链表在物理上是不连续, 随机存储的, 所以命中率会低一些.


有了上面的基础, 请看具体代码实现:

// 从SpanList或者page cache获取一个非空的span
Span* CentralCache::GetOneSpan(SpanList& list, size_t size)
{// 判断当前的SpanList是否有非空的spanSpan* it = list.Begin();while (it != list.End()){if (it->_freeList != nullptr){return it;}else{it = it->_next;}}// 解除桶锁, 方便thread cache释放内存回来list._mtx.unlock();// 走到这说明需要从page cache获取一个非空的spanPageCache::GetInstance()->_pageMtx.lock();Span* span = PageCache::GetInstance()->NewSpan(SizeClass::NumMovePage(size));PageCache::GetInstance()->_pageMtx.unlock();// 计算大块内存所在的地址和大小char* start = (char*)(span->_pageId << PAGE_SHIFT); // 大块内存的起始地址size_t bytes = span->_n << PAGE_SHIFT; // 大块内存的大小 - 字节数char* end = start + bytes; // 大块内存的结束地址// 将获取到的span切成小块内存链接起来// 先切一块下来做头, 方便尾插span->_freeList = start;void* tail = span->_freeList;start += size;while (start < end){NextObj(tail) = start;tail = start;start += size;}list._mtx.lock(); // 加在前面// 将切好的span插入central cache的SpanList中list.PushFront(span);return span;
}

对于上面的代码我们还需要注意的是加锁解锁问题, 了解代码后就会知道, 当 central cache 向 page cache 申请内存时, central cache 对应的哈希桶是处于加锁状态的, 那在访问 page cache 之前我们应不应该把 central cache 对应的桶锁解掉呢?

这里建议在访问 page cache 前, 先把 central cache 对应的桶锁解掉, 虽然此时 central cache 的这个桶当中是没有内存供其他 thread cache 申请的, 但 thread cache 除了在 central cache中申请内存外, 还会释放内存回到 central cache 中, 如果在访问 page cache 前将 central cache 对应的桶锁解掉, 那么此时如果有 thread cache 想要归还内存给 central cache 的这个桶时就不会被阻塞.

还需要注意的是在调用 NewSpan 函数之前, 还需要将 page cache 的大锁加上, 当申请到k页的span后, 我们需要将 page cache 的大锁解掉, 但此时我们不需要立刻获取到 central cache 中对应的桶锁. 因为 central cache 拿到k页的span后还会对其进行切分操作, 因此我们可以在span切好后需要将其挂到 central cache 对应的桶上之前, 获取对应的桶锁.


获取一个k页的span

当我们调用 GetOneSpan 函数从 central cache 的某个哈希桶获取一个非空的span时, 如果遍历哈希桶中的双向链表后发现其中没有span, 或该双向链表中的span都为空, 那么此时 central cache 就需要向 page cache 申请若干页的span.

因为 page cache 是直接按照页数进行映射的, 因此我们要从 page cache 中获取一个k页的span, 就应该直接去找 page cache 的第k号桶, 如果第k号桶中有span, 那我们直接头删一个span返回给 central cache 就行了, 如果没有, 继续向下遍历直至最后一个桶, 在这期间, 如果第n号桶挂有n页的span非空, 将其分裂成一个k页的span和一个n-k页的span.

但是如果遍历到了最后一个桶, 依然没有发现非空的span, 此时需要向系统堆申请一个大页的span, 我们这里申请的是128页的span, 直接使用我们封装的 SystemAlloc 函数即可.

// 获取一个k页的span
Span* PageCache::NewSpan(size_t k)
{assert(k > 0 && k < NPAGES);// 判断k号桶是否有非空的spanif (!_spanLists[k].Empty()){_spanLists[k].PopFront();}// 遍历k+1到最后一个桶for (size_t i = k + 1; i < NPAGES; i++){if (!_spanLists[i].Empty()){Span* kSpan = new Span;Span* nSpan = _spanLists[i].PopFront();// 将nSpan头部的k页切下来kSpan->_pageId = nSpan->_pageId;kSpan->_n = k;nSpan->_pageId += k;nSpan->_n -= k;// 将nSpan剩下的挂到其映射的位置_spanLists[nSpan->_n].PushFront(nSpan);return kSpan;}}// 走到这说明需要向系统堆申请128页的spanSpan* bigSpan = new Span;void* ptr = SystemAlloc(NPAGES - 1);bigSpan->_pageId = (PAGE_ID)ptr >> PAGE_SHIFT;bigSpan->_n = NPAGES - 1;_spanLists[bigSpan->_n].PushFront(bigSpan);// 尽量避免代码重复, 递归调用自己return NewSpan(k);
}

相关文章:

page cache设计及实现

你好&#xff0c;我是安然无虞。 page cache的设计及实现 page cache 本质上也是一个哈希桶, 它是按照页的数量进行映射的. 当 central cache 向 page cache 申请内存时, page cache 先检查对应位置是否有span, 如果没有则向更大页去寻找一个span, 如果找到则分裂成两个. 比如…...

使用seata来解决分布式事务

文章目录 目录 文章目录 前言 一、Seata的执行流程如下 二、使用步骤 三、配置微服务客户端 总结 前言 Seata部署指南 Seata 是一款开源的分布式事务解决方案&#xff0c;致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模…...

推荐一款新的自动化测试框架:DrissionPage

今天给大家推荐一款基于Python的网页自动化工具&#xff1a;DrissionPage。这款工具既能控制浏览器&#xff0c;也能收发数据包&#xff0c;甚至能把两者合而为一&#xff0c;简单来说&#xff1a;集合了WEB浏览器自动化的便利性和 requests 的高效率。 一、DrissionPage产生背…...

MQ系列面试

先来说说什么是MQ&#xff0c;MQ与多线程之间的区别MQ是消息中间件 可以实现异步 多线程也可以实现异步使用传统http协议方式调用接口存在的缺点如果服务器端没有及时的响应给客户端的时候&#xff0c;容易造成客户端阻塞等待。服务器响应超时 客户端发送重试机制 需要考虑避免…...

一句话设计模式2:原型模式

原型模式:每次得到一个新对象。 文章目录 原型模式:每次得到一个新对象。前言一、原型模式和new的区别二、如何实现原型模式1. 什么clone接口2. 开始使用,并验证浅clone效果3. 深度clone(也就是address也要复制一份)总结前言 原型模式可以说是目前接触的设计模式中,比较无用的…...

c++11特性与c++17特性

1、自动类型推导auto // C11 auto func1() -> int // 需要指定返回值类型 {return 10; }auto func2() -> std::function<void()> {auto lambda []() { };return lambda; }// c17 // 之后无需指定返回值类型 auto func1() {return 10; }auto func2() {auto lambda…...

Redis02: Redis基础命令

一、基础命令 先启动redis服务&#xff0c;使用redis-cli客户端连到redis数据库里面 1. 获取符合规则的键: keys 要点&#xff1a; &#xff08;1&#xff09;keys 后面可以指定正则表达式 &#xff08;2&#xff09;在生产环境下建议禁用keys命令&#xff0c;因为这个命令会查…...

MDK的HardFault硬件异常和NMI异常原因总结

发出来&#xff0c;出现问题自行比对&#xff0c;现在一些代码&#xff0c;也会对这个进行分析。硬件异常原因&#xff1a; Unaligned load or store Load 或者 store 指令访问未对齐地址 Undefined Instruction 执行 ARM 未定义的指令 EPSR Fault 当前程序没有在 Thumb 状态下…...

视频图像质量诊断

视频图像质量诊断有哪些原理&#xff0c;视频图像质量诊断有哪些算法&#xff1f; 视频图像质量诊断技术支持对视频黑屏、视频干扰、视频卡顿、视频遮挡、亮度异常、图像偏色、视频模糊、视频冻结、视频抖动、场景变更、无字符叠加等20种视频图像质量异常进行诊断&#xff0c;…...

make、Makefile项目自动化构建工具

环境&#xff1a;centos7.6&#xff0c;腾讯云服务器Linux文章都放在了专栏&#xff1a;【Linux】欢迎支持订阅&#x1f339;前言自动化构建工具是干什么的呢&#xff1f;主要是为了让我们对指令进行一些设置&#xff0c;就比如说&#xff0c;假如一个项目里有很多个源文件&…...

Linux系统之Uboot、Kernel、Busybox思考之一

目录 一 基础环境 1 硬件基础环境 2 软件基础环境 2.1 Uboot 2.2 内核 2.3 文件系统 二 启动过程 1 2 3 4 5 6 7 一 基础环境 1 硬件基础环境 CPU、内存和FLASH为基础环境&#xff0c;有了这三样&#xff0c;程序就可以跑起来。在此基础上补充各种外设&#xff…...

CCNP350-401学习笔记(401-450题)

401、What is the function of vBond in a Cisco SDWAN deployment? A. initiating connections with SD-WAN routers automatically B. pushing of configuration toward SD-WAN routersC. onboarding of SDWAN routers into the SD-WAN overlay D. gathering telemetry dat…...

一文带你看透前端世界里的日期时间,对就是Date

很高兴我们能够通过不同空间&#xff0c;不同时间&#xff0c;通过这篇博客相识&#xff0c;那一定是一种缘分&#xff0c;一种你和狗哥的缘分。今天我希望通过这篇博客对我所熟知的前端世界里的日期时间做一个汇总&#xff0c;不止是代码上的汇总哦&#xff01; 目录 一、时区…...

易基因|RRBS单碱基绘制580种动物的基因组规模DNA甲基化谱:Nature子刊

大家好&#xff0c;这里是专注表观组学十余年&#xff0c;领跑多组学科研服务的易基因。2023年01月16日&#xff0c;奥地利科学院分子医学研究中心(CeMM)研究团队在《Nat Commun》杂志发表了题为“Comparative analysis of genome-scale, base-resolution DNA methylation prof…...

面试官:能用JavaScript手写一个bind函数吗

经常会看到网上各种手写bind的教程&#xff0c;下面是我在自己实现手写bind的过程中遇到的问题与思考。如果对于如何实现一个手写bind还有疑惑的话&#xff0c;那么可以先看看上面两篇文章。 手写bind vs 原生bind 我们先使用一个典型的手写bind的例子&#xff0c;代码如下&a…...

美国拟发布纽扣电池或硬币电池安全标准和通知要求ANSI C18. 3M

2023年2月10日&#xff0c;美国向WTO提交G/TBT/N/USA/1964号通报&#xff0c;拟发布纽扣电池或硬币电池以及含有此类电池的消费品的安全标准和通知要求&#xff0c;征求意见截止日期为2023年3月13日&#xff0c;拟通过日期和生效日期待定。联[1]系 拟定规则通知根据H.R.5313瑞…...

双因素方差分析

一、案例与数据 一家大型商业银行在多地区设有分行&#xff0c;其业务主要是进行基础设施建设&#xff0c;国家重点项目建设&#xff0c;固定资产投资等项目的贷款。近年来&#xff0c;该银行的贷款额平稳增长&#xff0c;但不良贷款额也有较大比例的提高&#xff0c;这给银行…...

[ vulhub漏洞复现篇 ] Drupal XSS漏洞 (CVE-2019-6341)

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 _PowerShell &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 &#x1f389;点赞➕评论➕收藏 养成习…...

「TCG 规范解读」第8章 TPM工作组 TPM 1.2中 SHA1的使用

可信计算组织&#xff08;Ttrusted Computing Group,TCG&#xff09;是一个非盈利的工业标准组织&#xff0c;它的宗旨是加强在相异计算机平台上的计算环境的安全性。TCG于2003年春成立&#xff0c;并采纳了由可信计算平台联盟&#xff08;the Trusted Computing Platform Alli…...

熵权法计算权重

文章目录1. 多属性决策问题2. 熵&#xff08;entropy&#xff09;3. 信息熵4. 熵权法5. 熵权法的实现基于信息论的熵值法是根据各指标所含信息有序程度的差异性来确定指标权重的客观赋权方法&#xff0c;仅依赖于数据本身的离散程度。熵用于度量不确定性&#xff0c;指标的离散…...

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …...

【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器

——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的​​一体化测试平台​​&#xff0c;覆盖应用全生命周期测试需求&#xff0c;主要提供五大核心能力&#xff1a; ​​测试类型​​​​检测目标​​​​关键指标​​功能体验基…...

c++ 面试题(1)-----深度优先搜索(DFS)实现

操作系统&#xff1a;ubuntu22.04 IDE:Visual Studio Code 编程语言&#xff1a;C11 题目描述 地上有一个 m 行 n 列的方格&#xff0c;从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子&#xff0c;但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...

MVC 数据库

MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...

鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/

使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题&#xff1a;docker pull 失败 网络不同&#xff0c;需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...

laravel8+vue3.0+element-plus搭建方法

创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...

Go 语言并发编程基础:无缓冲与有缓冲通道

在上一章节中&#xff0c;我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道&#xff0c;它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好&#xff0…...

群晖NAS如何在虚拟机创建飞牛NAS

套件中心下载安装Virtual Machine Manager 创建虚拟机 配置虚拟机 飞牛官网下载 https://iso.liveupdate.fnnas.com/x86_64/trim/fnos-0.9.2-863.iso 群晖NAS如何在虚拟机创建飞牛NAS - 个人信息分享...

React从基础入门到高级实战:React 实战项目 - 项目五:微前端与模块化架构

React 实战项目&#xff1a;微前端与模块化架构 欢迎来到 React 开发教程专栏 的第 30 篇&#xff01;在前 29 篇文章中&#xff0c;我们从 React 的基础概念逐步深入到高级技巧&#xff0c;涵盖了组件设计、状态管理、路由配置、性能优化和企业级应用等核心内容。这一次&…...

EasyRTC音视频实时通话功能在WebRTC与智能硬件整合中的应用与优势

一、WebRTC与智能硬件整合趋势​ 随着物联网和实时通信需求的爆发式增长&#xff0c;WebRTC作为开源实时通信技术&#xff0c;为浏览器与移动应用提供免插件的音视频通信能力&#xff0c;在智能硬件领域的融合应用已成必然趋势。智能硬件不再局限于单一功能&#xff0c;对实时…...