PageCache页缓存

一.PageCache基本结构

1.PageCache任务

PageCache负责使用系统调用向系统申请页的内存,给CentralCache分配大块儿的内存,以及合并前后页空闲的内存,整体也是一个单例,需要加锁.

PageCache桶的下标按照页号进行映射,每个桶里span的页数即为下标大小.

2.基本结构

8d4f535f37524e3b95fec5aa7d958676.png

当每个线程的ThreadCache没有内存时都会向central cache申请,此时多个线程的ThreadCache如果访问的不是CentralCache的同一个桶,那么这些线程是可以同时进行访问的。
这时CentralCache的多个桶就可能同时向PageCache申请内存的,所以PageCache也是存在线程安全问题的,因此在访问PageCache时也必须要加锁

  在PageCache这里我们不能使用桶锁,因为当CentralCache向PageCache申请内存时,PageCache可能会将其他桶当中大页的span切小后再给CentralCache。此外,当CentralCache将某个span归还给PageCache时,PageCache也会尝试将该span与其他桶当中的span进行合并
即PageCache内部存在多个桶之间的交互,所以要么所有桶都加锁,要么给PageCache加一把大锁

  也就是说,在访问PageCache时,我们可能需要访问PageCache中的多个桶,如果PageCache用桶锁就会出现大量频繁的加锁和解锁,导致程序的效率低下。因此我们在访问PageCache时使用没有使用桶锁,而是用一个大锁将整个PageCache给锁住。

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

二.class PageCache

20ce72e5f4134d42baafb22747e60f50.png

1283f6470fda4005a313b73a25b1048d.png

三.系统调用接口封装

// 直接去堆上按页申请空间
inline static void* SystemAlloc(size_t kpage)
{
#ifdef _WIN32void* ptr = VirtualAlloc(0, kpage << 13, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#else// linux下brk mmap等size_t bytes = kpage << 13; // kpage 是页数,每页大小为 8 KB(1 << 13 字节)ptr = mmap(0, bytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);if (ptr == MAP_FAILED) {ptr = nullptr; // mmap 失败时返回 MAP_FAILED,我们需要将其转换为 nullptr}
#endifif (ptr == nullptr)throw std::bad_alloc();return ptr;
}inline static void SystemFree(void* ptr)
{
#ifdef _WIN32VirtualFree(ptr, 0, MEM_RELEASE);
#else// sbrk unmmap等
#endif
}

三.NewSpan获取一个k页的span

// 获取一个K页的span
Span* PageCache::NewSpan(size_t k)
{assert(k > 0);// 大于128 page的直接向堆申请if (k >= NPAGES){void* ptr = SystemAlloc(k);//Span* span = new Span;Span* span = _spanPool.New();span->_pageId = (PAGE_ID)ptr >> PAGE_SHIFT;span->_n = k;//_idSpanMap[span->_pageId] = span;_idSpanMap.set(span->_pageId, span);return span;}///1._spanLists中有k个page的spanif (!_spanLists[k].Empty()){Span* kSpan = _spanLists[k].PopFront();// 建立id和span的映射,方便central cache回收小块内存时,查找对应的spanfor (PAGE_ID i = 0; i < kSpan->_n; ++i){//_idSpanMap[kSpan->_pageId + i] = kSpan;_idSpanMap.set(kSpan->_pageId + i, kSpan);}return kSpan;}//2._spanLists[k]为空,从更大的span中寻找for (size_t i = k + 1; i < NPAGES; ++i){if (!_spanLists[i].Empty()){Span* nSpan = _spanLists[i].PopFront();//Span* kSpan = new Span;Span* kSpan = _spanPool.New();// 在nSpan的头部切一个k页下来,k页span返回,nSpan再挂到对应映射的位置kSpan->_pageId = nSpan->_pageId;kSpan->_n = k;nSpan->_pageId += k;nSpan->_n -= k;_spanLists[nSpan->_n].PushFront(nSpan);// 存储nSpan的首位页号跟nSpan映射,方便PageCache回收内存时向前向后合并查找//_idSpanMap[nSpan->_pageId] = nSpan;//_idSpanMap[nSpan->_pageId + nSpan->_n - 1] = nSpan;_idSpanMap.set(nSpan->_pageId, nSpan);_idSpanMap.set(nSpan->_pageId + nSpan->_n - 1, nSpan);// 建立id和span的映射,方便CentralCache回收小块内存时,查找对应的spanfor (PAGE_ID i = 0; i < kSpan->_n; ++i){//_idSpanMap[kSpan->_pageId + i] = kSpan;_idSpanMap.set(kSpan->_pageId + i, kSpan);}return kSpan;}}//3.整个_spanLists都为空,向堆申请一个NPAGES(128)大小的span//Span* bigSpan = new Span;Span* bigSpan = _spanPool.New();void* ptr = SystemAlloc(NPAGES - 1);bigSpan->_pageId = (PAGE_ID)ptr >> PAGE_SHIFT;bigSpan->_n = NPAGES - 1;_spanLists[bigSpan->_n].PushFront(bigSpan);//4.递归调用NewSpan重新切分return NewSpan(k);
}

f87e23410b324943a7480520097be661.png

 

  1. 因为PageCache是直接按照页数进行映射的,因此我们要从PageCache获取一个k页的span,就应该直接先去找PageCache的第k号桶,如果第k号桶中有span,那我们直接头删一个span返回给CentralCache就行了.
  2. 如果PageCache的第k号桶中没有span,我们就应该继续找后面的桶,只要后面任意一个桶中有一个n页span,我们就可以将其切分成一个k页的span和一个n-k页的span,然后将切出来k页的span返回给CentralCache,再将n-k页的span挂到PageCache的第n-k号桶即可。
  3. 但如果后面的桶中都没有span,此时我们就需要向堆申请一个128页的span了,在向堆申请内存时,直接调用我们封装的SystemAlloc函数即可。  

        需要注意的是,向堆申请内存后得到的是这块内存的起始地址,此时我们需要将该地址转换为页号。由于我们向堆申请内存时都是按页进行申请的,因此我们直接将该地址除以一页的大小即可得到对应的页号。
        递归调用:将申请到的128页的span挂到PageCache对应的哈希桶128号中,然后递归调用该函数,此时在往后找span时就一定会在第128号桶中找到该span,然后进行切分。
这里可以使用递归锁,或者在NewSpan外部加锁,防止递归时产生死锁.

  这里其实有一个问题:当CentralCache向PageCache申请内存时,CentralCache对应的哈希桶是处于加锁的状态的,那在访问PageCache之前我们应不应该把CentralCache对应的桶锁解掉呢?

  这里建议在访问PageCache前,先把CentralCache对应的桶锁解掉。
虽然此时CentralCache的这个桶当中是没有内存供其他ThreadCache申请的,但ThreadCache除了申请内存还会释放内存,如果在访问PageCache前将CentralCache对应的桶锁解掉,那么此时当其他ThreadCache想要归还内存到CentralCache的这个桶时就不会阻塞
因此在调用NewSpan函数之前,我们需要先将CentralCache对应的桶锁解掉,然后再将PageCache的大锁加上,当申请到k页的span后,我们需要将PageCache的大锁解掉,CentralCache拿到k页的span后对其进行切分操作(该过程不需要加锁),在span切好后需要将其挂到CentralCache对应的桶上时,再获取对应的桶锁。

四.MapObjectToSpan哈希

 PageCache在合并span时,是需要通过页号获取到对应的span的,因此要华北库页号与span之间的映射关系,用MapObjectToSpan存储.


Span* PageCache::MapObjectToSpan(void* obj)
{PAGE_ID id = ((PAGE_ID)obj >> PAGE_SHIFT);Span* ret = (Span*)_idSpanMap.get(id);assert(ret != nullptr);return ret;//std::unique_lock<std::mutex> lock(_pageMtx);//auto ret = _idSpanMap.find(id);//if (ret != _idSpanMap.end())//{//	return ret->second;//}//else//{//	assert(false);//	return nullptr;//}
}

五.ReleaseSpanToPageCache归还Span

void PageCache::ReleaseSpanToPageCache(Span* span)
{// 大于128 page的直接还给堆if (span->_n > NPAGES - 1){void* ptr = (void*)(span->_pageId << PAGE_SHIFT);SystemFree(ptr);//delete span;_spanPool.Delete(span);return;}// 对span前后的页,尝试进行合并,缓解内存碎片问题while (1){PAGE_ID prevId = span->_pageId - 1;//1.前一个page还没有被申请auto ret = (Span*)_idSpanMap.get(prevId);if (ret == nullptr)break;//2.前面相邻页的span在使用,不合并了Span* prevSpan = ret;if (prevSpan->_isUse == true)break;//3.合并出超过128页的span无需合并if (prevSpan->_n + span->_n > NPAGES - 1)break;span->_pageId = prevSpan->_pageId;span->_n += prevSpan->_n;_spanLists[prevSpan->_n].Erase(prevSpan);//delete prevSpan;_spanPool.Delete(prevSpan);}// 向后合并while (1){PAGE_ID nextId = span->_pageId + span->_n;auto ret = (Span*)_idSpanMap.get(nextId);if (ret == nullptr)break;Span* nextSpan = ret;if (nextSpan->_isUse == true)break;if (nextSpan->_n + span->_n > NPAGES - 1)break;span->_n += nextSpan->_n;_spanLists[nextSpan->_n].Erase(nextSpan);//delete nextSpan;_spanPool.Delete(nextSpan);}_spanLists[span->_n].PushFront(span);span->_isUse = false;//_idSpanMap[span->_pageId] = span;//_idSpanMap[span->_pageId+span->_n-1] = span;_idSpanMap.set(span->_pageId, span);_idSpanMap.set(span->_pageId + span->_n - 1, span);
}

 如果CentralCache中有某个span的_useCount减到0了,CentralCache就需要将这个span还给PageCache.
  这个过程看似是非常简单的,PageCache只需将还回来的span挂到对应的哈希桶上就行了。但实际为了缓解内存碎片的问题,PageCache还需要尝试将还回来的span与其他空闲的span进行合并

 合并的过程可以分为向前合并和向后合并.
如果还回来的span的起始页号是num,该span所管理的页数是n.
那么在向前合并时,就需要判断第num-1页对应span是否空闲,如果空闲则可以将其进行合并,并且合并后还需要继续向前尝试进行合并,直到不能进行合并为止.
而在向后合并时,就需要判断第num+n页对应的span是否空闲,如果空闲则可以将其进行合并,并且合并后还需要继续向后尝试进行合并,直到不能进行合并为止.

  因此PageCache在合并span时,是需要通过页号获取到对应的span的,这就是我们要把页号与span之间的映射关系存储到PageCache的原因.

  但需要注意的是,当我们通过页号找到其对应的span时,这个span此时可能挂在PageCache,也可能挂在CentralCache。而在合并时我们只能合并挂在PageCache的span,因为挂在CentralCache的span当中的对象正在被其他线程使用。

  可是我们不能通过span结构当中的_useCount成员,来判断某个span到底是在CentralCache还是在PageCache.因为当CentralCache刚向PageCache申请到一个span时,这个span的_useCount就是等于0的,这时可能当我们正在对该span进行切分的时候,PageCache就把这个span拿去进行合并了,这显然是不合理的。  
        因此,我们可以在span结构中再增加一个_isUse成员,用于标记这个span是否正在被使用,而当一个span结构被创建时我们默认该span是没有被使用的。
        即在PageCache中,_isUse=false,在CentralCache中为true

 

由于在合并PageCache当中的span时,需要通过页号找到其对应的span,而一个span是在被分配给CentralCache时,才建立的各个页号与span之间的映射关系,因此PageCache当中的span也需要建立页号与span之间的映射关系。

  与CentralCache中的span不同的是,在PageCache中,只需建立一个span的首尾页号与该span之间的映射关系。因为当一个span在尝试进行合并时,如果是往前合并,那么只需要通过一个span的尾页找到这个span,如果是向后合并,那么只需要通过一个span的首页找到这个span。也就是说,在进行合并时我们只需要用到span与其首尾页之间的映射关系就够了。  
        因此当我们申请k页的span时,如果是将n页的span切成了一个k页的span和一个n-k页的span,我们除了需要建立k页span中每个页与该span之间的映射关系之外,还需要建立剩下的n-k页的span与其首尾页之间的映射关系。

PageCache.cpp/.h

PageCache.cpp

#include "PageCache.h"PageCache PageCache::_sInst;
std::mutex PageCache::_pageMtx;// 获取一个K页的span
Span* PageCache::NewSpan(size_t k)
{assert(k > 0);// 大于128 page的直接向堆申请if (k >= NPAGES){void* ptr = SystemAlloc(k);//Span* span = new Span;Span* span = _spanPool.New();span->_pageId = (PAGE_ID)ptr >> PAGE_SHIFT;span->_n = k;//_idSpanMap[span->_pageId] = span;_idSpanMap.set(span->_pageId, span);return span;}///1._spanLists中有k个page的spanif (!_spanLists[k].Empty()){Span* kSpan = _spanLists[k].PopFront();// 建立id和span的映射,方便central cache回收小块内存时,查找对应的spanfor (PAGE_ID i = 0; i < kSpan->_n; ++i){//_idSpanMap[kSpan->_pageId + i] = kSpan;_idSpanMap.set(kSpan->_pageId + i, kSpan);}return kSpan;}//2._spanLists[k]为空,从更大的span中寻找for (size_t i = k + 1; i < NPAGES; ++i){if (!_spanLists[i].Empty()){Span* nSpan = _spanLists[i].PopFront();//Span* kSpan = new Span;Span* kSpan = _spanPool.New();// 在nSpan的头部切一个k页下来,k页span返回,nSpan再挂到对应映射的位置kSpan->_pageId = nSpan->_pageId;kSpan->_n = k;nSpan->_pageId += k;nSpan->_n -= k;_spanLists[nSpan->_n].PushFront(nSpan);// 存储nSpan的首位页号跟nSpan映射,方便PageCache回收内存时向前向后合并查找//_idSpanMap[nSpan->_pageId] = nSpan;//_idSpanMap[nSpan->_pageId + nSpan->_n - 1] = nSpan;_idSpanMap.set(nSpan->_pageId, nSpan);_idSpanMap.set(nSpan->_pageId + nSpan->_n - 1, nSpan);// 建立id和span的映射,方便CentralCache回收小块内存时,查找对应的spanfor (PAGE_ID i = 0; i < kSpan->_n; ++i){//_idSpanMap[kSpan->_pageId + i] = kSpan;_idSpanMap.set(kSpan->_pageId + i, kSpan);}return kSpan;}}//3.整个_spanLists都为空,向堆申请一个NPAGES(128)大小的span//Span* bigSpan = new Span;Span* bigSpan = _spanPool.New();void* ptr = SystemAlloc(NPAGES - 1);bigSpan->_pageId = (PAGE_ID)ptr >> PAGE_SHIFT;bigSpan->_n = NPAGES - 1;_spanLists[bigSpan->_n].PushFront(bigSpan);//4.递归调用NewSpan重新切分return NewSpan(k);
}Span* PageCache::MapObjectToSpan(void* obj)
{PAGE_ID id = ((PAGE_ID)obj >> PAGE_SHIFT);Span* ret = (Span*)_idSpanMap.get(id);assert(ret != nullptr);return ret;//std::unique_lock<std::mutex> lock(_pageMtx);//auto ret = _idSpanMap.find(id);//if (ret != _idSpanMap.end())//{//	return ret->second;//}//else//{//	assert(false);//	return nullptr;//}
}void PageCache::ReleaseSpanToPageCache(Span* span)
{// 大于128 page的直接还给堆if (span->_n > NPAGES - 1){void* ptr = (void*)(span->_pageId << PAGE_SHIFT);SystemFree(ptr);//delete span;_spanPool.Delete(span);return;}// 对span前后的页,尝试进行合并,缓解内存碎片问题while (1){PAGE_ID prevId = span->_pageId - 1;//1.前一个page还没有被申请auto ret = (Span*)_idSpanMap.get(prevId);if (ret == nullptr)break;//2.前面相邻页的span在使用,不合并了Span* prevSpan = ret;if (prevSpan->_isUse == true)break;//3.合并出超过128页的span无需合并if (prevSpan->_n + span->_n > NPAGES - 1)break;span->_pageId = prevSpan->_pageId;span->_n += prevSpan->_n;_spanLists[prevSpan->_n].Erase(prevSpan);//delete prevSpan;_spanPool.Delete(prevSpan);}// 向后合并while (1){PAGE_ID nextId = span->_pageId + span->_n;auto ret = (Span*)_idSpanMap.get(nextId);if (ret == nullptr)break;Span* nextSpan = ret;if (nextSpan->_isUse == true)break;if (nextSpan->_n + span->_n > NPAGES - 1)break;span->_n += nextSpan->_n;_spanLists[nextSpan->_n].Erase(nextSpan);//delete nextSpan;_spanPool.Delete(nextSpan);}_spanLists[span->_n].PushFront(span);span->_isUse = false;//_idSpanMap[span->_pageId] = span;//_idSpanMap[span->_pageId+span->_n-1] = span;_idSpanMap.set(span->_pageId, span);_idSpanMap.set(span->_pageId + span->_n - 1, span);
}

PageCache.h

#pragma once#include "Common.h"
#include "ObjectPool.h"
#include "PageMap.h"class PageCache
{
public:static PageCache* GetInstance(){return &_sInst;}// 获取从内存对象到span的映射Span* MapObjectToSpan(void* obj);// 释放空闲span回到Pagecache,并合并相邻的spanvoid ReleaseSpanToPageCache(Span* span);// 获取一个K页的spanSpan* NewSpan(size_t k);static std::mutex _pageMtx;
private:SpanList _spanLists[NPAGES];ObjectPool<Span> _spanPool;//std::unordered_map<PAGE_ID, Span*> _idSpanMap;
#ifdef _WIN64TCMalloc_PageMap3<64 - PAGE_SHIFT> _idSpanMap;
#elseTCMalloc_PageMap1<32 - PAGE_SHIFT> _idSpanMap;
#endifprivate:PageCache(){}PageCache(const PageCache&) = delete;static PageCache _sInst;
};

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/41814.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何使用uer做多分类任务

如何使用uer做多分类任务 语料集下载 找到这里点击即可 里面是这有json文件的 因此我们对此要做一些处理&#xff0c;将其转为tsv格式 # -*- coding: utf-8 -*- import json import csv import chardet# 检测文件编码 def detect_encoding(file_path):with open(file_path,…

Vatee万腾平台:智能生活的新选择

在科技飞速发展的今天&#xff0c;智能生活已经不再是遥不可及的梦想&#xff0c;而是逐渐渗透到我们日常生活的方方面面。Vatee万腾平台&#xff0c;作为智能科技领域的佼佼者&#xff0c;正以其创新的技术、丰富的应用场景和卓越的用户体验&#xff0c;成为智能生活的新选择&…

vue学习笔记(购物车小案例)

用一个简单的购物车demo来回顾一下其中需要注意的细节。 先看一下最终效果 功能&#xff1a; &#xff08;1&#xff09;全选按钮和下面的商品项的选中状态同步&#xff0c;当下面的商品全部选中时&#xff0c;全选勾选&#xff0c;反之&#xff0c;则不勾选。 &#xff08…

51单片机嵌入式开发:2、STC89C52操作GPIO口LED灯

STC89C52操作GPIO口LED灯 1 芯片介绍1.1 芯片类型1.2 芯片系列说明 2 GPIO引脚寄存器说明3 GPIO操作3.1 GPIO输入3.2 GPIO输出3.3 GPIO流水灯3.4 Protues仿真 4 总结 1 芯片介绍 1.1 芯片类型 芯片采用宏晶科技品牌下的STC89C52RC单片机 选择STC89C52RC系列STC89C58RD系列单片…

Pycharm远程连接GPU(内容:下载安装Pycharm、GPU租借、配置SSH、将代码同步到镜像、命令行操控远程镜像、配置远程GPU解释器)

目录 windows下载安装pycharmGPU租借网站AutoDlfeaturize好易智算 GPU租借GPU选择选择镜像充值 然后创建镜像创建成功 复制SSH登录信息 远程进入镜像 在Pycharm中进行ssh连接新建SFTP配置SSH复制ssh根据复制的信息填写ssh配置测试连接 将代码同步到远程镜像上设置mappings将本地…

大语言模型与知识图谱结合发展方向

引言 在人工智能的发展历程中&#xff0c;大语言模型&#xff08;LLM&#xff09;的出现标志着一个重要的转折点。随着深度学习技术的突破和计算能力的提升&#xff0c;LLM以其前所未有的规模和复杂性&#xff0c;开启了迈向人工通用智能&#xff08;AGI&#xff09;的新浪潮。…

STM32利用FreeRTOS实现4个led灯同时以不同的频率闪烁

在没有接触到FreeRTOS时&#xff0c;也没有想过同时叫两个或两个以上的led灯闪烁的想法&#xff0c;接触后&#xff0c;发现如果想叫两个灯同时以不同的频率闪烁&#xff0c;不能说是不可能&#xff0c;就算是做到了也要非常的麻烦。但是学习了FreeRTOS后&#xff0c;发现要想同…

使用WinSCP工具连接Windows电脑与Ubuntu虚拟机实现文件共享传输

一。环境配置 1.首先你的Windows电脑上安装了VMware虚拟机&#xff0c;虚拟机装有Ubuntu系统&#xff1b; 2.在你的windows电脑安装了WinSCP工具&#xff1b; 3.打开WinSCP工具默认是这样 二。设置WinSCP连接 打开WinSCP&#xff0c;点击新标签页&#xff0c;进入到如下图的…

【学术会议征稿】2024年工业自动化与机器人国际学术会议(IAR 2024)

2024年工业自动化与机器人国际学术会议&#xff08;IAR 2024&#xff09; 2024 International Conference on Industrial Automation and Robotics 2024年工业自动化与机器人国际学术会议&#xff08;IAR 2024&#xff09;将于2024年10月18-20日在新加坡隆重召开。会议将围绕…

三丰云评测:免费虚拟主机与免费云服务器的全面对比

三丰云是一家知名的互联网服务提供商&#xff0c;专注于虚拟主机和云服务器的服务。在互联网技术日新月异的今天&#xff0c;选择一个优质的云服务提供商至关重要。本次评测将重点对比三丰云的免费虚拟主机和免费云服务器&#xff0c;帮助用户更好地选择适合自己需求的服务。首…

0 TMS320F28379D 开坑

开坑原因 最近开始做实验&#xff0c;实验室的主控采用的是F2812FPGA&#xff0c;属于够用但不好用的状态。FPGA用于生成调制信号&#xff0c;DSP完成采样和控制。师兄师姐研究拓扑及调制策略&#xff0c;对驱动数量以及驱动逻辑有比较高的要求&#xff0c;因此不好脱离FPGA&a…

CVE-2023-30212(xss漏洞)

简介 OURPHP版本<7.2.0存在XSS漏洞&#xff0c;攻击路径为/client/manage/ourphp_out.php。 过程 打开靶场 访问攻击路径/client/manage/ourphp_out.php 得到flag{354c7c41-cc23-4de5-be73-79cbbf384aba}

Multisim仿真-交流数字电压表

下图为整体的原理框图&#xff0c;交流电源经过整流滤波电路转换后&#xff0c;送入模数转换电路&#xff0c;经译码给到显示电路&#xff0c;由其显示交流电源的有效值。 信号发生器XFG1输出正弦波信号(峰峰值)&#xff0c;XMM1测量有效值&#xff0c;U6数码管显示有效值。仿真…

[BJDCTF 2nd]简单注入

sqlsqlsqlsqlsql又来喽 过滤了单双引号&#xff0c;等于符号&#xff0c;还有select等&#xff0c;但是这里没有二次注入 。扫描发现hint.txt 看出题人的意思是&#xff0c;得到密码即可获得flag。 select * from users where username$_POST["username"] and passw…

认识流式处理框架Apache Flink

目录 一、Apache Flink 的基础概念 1.1 Apache Flink是什么&#xff1f; 1.2 Flink的定义 二、Apache Flink 的发展史 2.1 Flink前身Stratosphere 2.2 Flink发展时间线及重大变更 三、Flink核心特性 3.1 批流一体化 3.2 同时支持高吞吐、低延迟、高性能 3.3 支持事件时…

Git 运用小知识

1.Git添加未完善代码的解决方法 1.1 Git只是提交未推送 把未完善的代码提交到本地仓库 只需点击撤销提交&#xff0c;提交的未完善代码会被撤回 代码显示未提交状态 1.2 Git提交并推送 把未完善的代码提交并推送到远程仓库 点击【未完善提交并推送】的结点选择还原提交&#x…

MinIO - 从 环境搭建 -> SpringBoot实战 -> 演示,掌握 Bucket 和 Object 操作

目录 开始 Docker 部署 MinIO 中的基本概念 SpringBoot 集成 MinIO 依赖 配置 MinIO 时间差问题报错 The difference between the request time and the servers time is too large MinIO 中对 Bucket&#xff08;文件夹&#xff09; 的操作 是否存在 / 创建 查询所有…

Apache Seata 源码分析Seata-XID传递 Dubbo篇

本文来自 Apache Seata官方文档&#xff0c;欢迎访问官网&#xff0c;查看更多深度文章。 本文来自 Apache Seata官方文档&#xff0c;欢迎访问官网&#xff0c;查看更多深度文章。 源码分析 Seata-XID 传递 Dubbo 篇 本文作者&#xff1a;FUNKYE(陈健斌),杭州某互联网公司主…

TQ15EG开发板教程:MPSOC创建fmcomms8工程

链接&#xff1a;https://pan.baidu.com/s/1jbuYs9alP2SaqnV5fpNgyg 提取码&#xff1a;r00c 本例程需要实现在hdl加no-OS系统中&#xff0c;通过修改fmcomms8/zcu102项目&#xff0c;实现在MPSOC两个fmc口上运行fmcomms8项目。 目录 1 下载文件与切换版本 2 编译fmcomms8项…

超越YOLO! RT-DETR 实时目标检测技术介绍

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…