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 _WIN32
	void* 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
	}
#endif

	if (ptr == nullptr)
		throw std::bad_alloc();

	return ptr;
}

inline static void SystemFree(void* ptr)
{
#ifdef _WIN32
	VirtualFree(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的span
	if (!_spanLists[k].Empty())
	{
		Span* kSpan = _spanLists[k].PopFront();

		// 建立id和span的映射,方便central cache回收小块内存时,查找对应的span
		for (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回收小块内存时,查找对应的span
			for (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的span
	if (!_spanLists[k].Empty())
	{
		Span* kSpan = _spanLists[k].PopFront();

		// 建立id和span的映射,方便central cache回收小块内存时,查找对应的span
		for (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回收小块内存时,查找对应的span
			for (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,并合并相邻的span
	void ReleaseSpanToPageCache(Span* span);
	// 获取一个K页的span
	Span* NewSpan(size_t k);

	static std::mutex _pageMtx;
private:
	SpanList _spanLists[NPAGES];
	ObjectPool<Span> _spanPool;

	//std::unordered_map<PAGE_ID, Span*> _idSpanMap;
#ifdef _WIN64
	TCMalloc_PageMap3<64 - PAGE_SHIFT> _idSpanMap;
#else
	TCMalloc_PageMap1<32 - PAGE_SHIFT> _idSpanMap;
#endif

private:
	PageCache(){}

	PageCache(const PageCache&) = delete;


	static PageCache _sInst;
};

 

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

文件、文本阅读与重定向、路径与理解指令——linux指令学习(一)

前言&#xff1a;本节内容标题虽然为指令&#xff0c;但是并不只是讲指令&#xff0c; 更多的是和指令相关的一些原理性的东西。 如果友友只想要查一查某个指令的用法&#xff0c; 很抱歉&#xff0c; 本节不是那种带有字典性质的文章。但是如果友友是想要来学习的&#xff0c;…

Python 空间和时间高效的二项式系数(Space and time efficient Binomial Coefficient)

这里函数采用两个参数n和k&#xff0c;并返回二项式系数 C(n, k) 的值。 例子&#xff1a; 输入&#xff1a; n 4 和 k 2 输出&#xff1a; 6 解释&#xff1a; 4 C 2 等于 4!/(2!*2!) 6 输入&#xff1a; n 5 和 k 2 输出&#xff1a; 10 解释&#xff1a; 5 C …

moonlight+sunshine+ParsecVDisplay ipad8-windows 局域网串流

1.sunshine PC 安装 2.设置任意账户密码登录 3.setting 里 network启用UPNP IPV4IPV6 save apply 4.ParsecVDisplay虚拟显示器安装 5.ipad appstore download moonlight 6.以ipad 8 为例 2160*1620屏幕分辨率 7.ParsecVDisplay里面 custom设置2160*1620 240hz&#xff0c;…

python conda查看源,修改源

查看源 conda config --show-sources 修改源 可以直接vim .condarc修改源&#xff0c;

CSS中 实现四角边框效果

效果图 关键代码 border-radius:10rpx ;background: linear-gradient(#fff, #fff) left top,linear-gradient(#fff, #fff) left top,linear-gradient(#fff, #fff) right top,linear-gradient(#fff, #fff) right top,linear-gradient(#fff, #fff) left bottom,linear-gradient(…

CentOS7安装Mysql8.4.0

简介 本文介绍了Linux CentOS系统下Mysql8.4.0的下载和安装方法 环境 (rpm -q centos-release) centos-release-7-2.1511.el7.centos.2.10.x86_64 正文 一、去官网下载Mysql8.4.0 下载参考我另一篇mysql5.7.4的安装 CentOS7.9安装Mysql5.7-m14_centos下mysql5.7下载-CSDN博客…

flutter开发实战-Webview及dispose关闭背景音

flutter开发实战-Webview及dispose关闭背景音 当在使用webview的时候&#xff0c;dispose需要关闭网页的背景音或者音效。 一、webview的使用 在工程的pubspec.yaml中引入插件 webview_flutter: ^4.4.2webview_cookie_manager: ^2.0.6Webview的使用代码如下 初始化WebView…

AJAX-个人版-思路步骤整理版

前置知识&#xff1a;老式的web创建工程方法就是创建项目然后添加web工件&#xff0c;然后添加lib依赖如&#xff1a;tomcat,servlet&#xff0c;等。 传统请求 对于传统请求操作&#xff1a;整体流程也就是创建静态页面&#xff0c; <!DOCTYPE html> <html lang&q…

每日一题~ leetcode 402 (贪心+单调栈)

click me! 这个贪心的推导在leetcode上已经很明确了。 click me! 删除k个数&#xff0c;可以先考虑删除一个数。这也是一种常见的思路。&#xff08;如果进行同样的操作多次&#xff0c;可以先只 考虑一次操作如何实现&#xff0c;或者他的影响。完成这一次操作后&#xff0c;…

MySQL基础篇(二)

如何创建一个数据库&#xff1a; create database 数据库名; 使用数据库&#xff1a; use 数据库名&#xff1b; 如何查看都有哪些数据库&#xff1a; use databases;//后面需要加s 容易忘 如何查看都有哪些表&#xff1a; use tables;//后面需要加s 如何清屏&#xff…

texStudio使用(小白)

原先使用overleaf在线编译&#xff0c;可能eps格式的图片太大导致需要充钱&#xff0c;所以考虑本地安装 安装教程参考B站视频&#xff1a;B站Latex本地编译器安装&#xff1a;TexLive TextStudio 踩到坑&#xff1a; 1. 编译器位置要选择对 因为BibTex选成了Biber导致出现无…

【反悔堆 优先队列 临项交换 决策包容性】630. 课程表 III

本文涉及知识点 贪心 反悔堆 优先队列 临项交换 Leetcode630. 课程表 III 这里有 n 门不同的在线课程&#xff0c;按从 1 到 n 编号。给你一个数组 courses &#xff0c;其中 courses[i] [durationi, lastDayi] 表示第 i 门课将会 持续 上 durationi 天课&#xff0c;并且必…

为什么建议 MySQL 数据库字段一定要设置 NOT NULL

1. 前言 建议 MySQL 数据库字段一定要设置 NOT NULL 这句建议你可能听好多人讲过&#xff0c;但是有没有仔细想过为什么别人这么说 &#xff1f; 在实际开发中&#xff0c;对使不使用 not null 很多人并没有一个明确的标准&#xff0c;要知道某个字段需不需要添加 not null&a…

深度学习:为什么说英伟达A100或RTX A6000等专业GPU比RTX 4090更适合深度学习呢?

目录 一、关键术语 CUDA cores&#xff08;CUDA内核&#xff09;&#xff1a; memory bandwidth&#xff08;内存带宽&#xff09;&#xff1a; 二、深度学习的显卡硬件要求 三、NVIDIA显卡A100、RTX A6000和RTX 4090对比 1、NVIDIA A100 2、NVIDIA RTX A6000 3、NVIDI…

BufferReader/BufferWriter使用时出现的问题

项目场景&#xff1a; 在一个文件中有一些数据&#xff0c;需要读取出来并替换成其他字符再写回文件中&#xff0c;需要用Buffer流。 问题描述 文件中的数据丢失&#xff0c;并且在读取前就为空&#xff0c;读取不到数据。 问题代码&#xff1a; File f new File("D:\\…

【算法专题】双指针算法

1. 移动零 题目分析 对于这类数组分块的问题&#xff0c;我们应该首先想到用双指针的思路来进行处理&#xff0c;因为数组可以通过下标进行访问&#xff0c;所以说我们不用真的定义指针&#xff0c;用下标即可。比如本题就要求将数组划分为零区域和非零区域&#xff0c;我们不…

51单片机基础10——串口实验

串口实验 51单片机串口实验1. 软硬件条件2. 串口实验2.1 单片机与PC 发送字符2.1.1 效果2.1.2 代码2.1.3 优化 2.3 串口接收数据(指令控制单片机)2.3.1 非中断方式实现2.3.2 中断方式实现 51单片机串口实验 1. 软硬件条件 单片机型号&#xff1a;STC89C52RC开发环境&#xff…

suricata7 rule加载(一)加载 action

suricata7.0.5 一、前提条件 1.1 关键字注册 main | --> SuricataMain|--> PostConfLoadedSetup|--> SigTableSetupsigmatch_table是一个全局数组&#xff0c;每个元素就是一个关键字节点&#xff0c;是对关键字如何处理等相关回调函数。非常重要的一个结构&#x…

DevOps实战:使用GitLab+Jenkins+Kubernetes(k8s)建立CI_CD解决方案

一.系统环境 本文主要基于Kubernetes1.21.9和Linux操作系统CentOS7.4。 服务器版本docker软件版本Kubernetes(k8s)集群版本CPU架构CentOS Linux release 7.4.1708 (Core)Docker version 20.10.12v1.21.9x86_64CI/CD解决方案架构图:CI/CD解决方案架构图描述:程序员写好代码之…

Python通过HiperMATRIX API写数据

PyCharm编程和调试 其中token 我偷懒了&#xff0c;只是调试&#xff0c;打开HiperMATRIX界面&#xff0c;登录&#xff0c;从浏览器console里面找到token value。 代码片段 import random, time, requests, jsonhipermatrix_api_url http://192.168.1.240:9030/api/edge-ma…