【C++项目】高并发内存池第三讲PageCache框架涉及+核心实现(上)

news2024/11/20 12:41:11

0

PageCache

  • 1. PageCache框架设计
    • 1.1整体设计
    • 1.2CentralCache向PageCache申请内存的逻辑设计
  • 2.PageCache的核心框架实现
    • 这里着重介绍一下CentralCache向PageCache申请内存的过程:

1. PageCache框架设计

1.1整体设计

回顾:
0
如图所示 内存申请的过程是逐步往下的,从ThreadCache—>CentralCache—>PageCache,内存不足时依次找下一级申请。

  • PageCache结构逻辑图:
    01
    一样是一个哈希桶结构,只不过不同的是,上面是一个双向带头的循环链表,每个链表挂的是一个个大块内存块span
    而且下标映射规则:1–128每个桶上挂的大块内存大小和下标相匹配,例如:一号桶 每个span的大下是1page,2号桶是2page依次类推…

1.2CentralCache向PageCache申请内存的逻辑设计

当CentralCache向PageCache申请内存时首先会计算出对应的pageid然后根据pageid去对应的桶获取一个span,当对应的桶没有对象时,会逐步往下一个桶遍历寻找空闲的span。举例说明当申请一个2页的内存时,首先会去PageCache中下标为2的桶去寻找空闲的span,该桶有空闲的span就直接返回给上一级;如果没有就会往下一个桶也就是下标为3 的桶寻找,此时找到了空闲的span,但是其大小为3页,这时就要进行切分把页切分成2页+1页,2页给上级对象返回,剩余的1页则根据映射规则挂到下标为1的桶,留着下次对象申请调用。

相关实现代码:

//获取一个K页的span
 Span* PageCache::Newspan(size_t k)
{
	assert(k > 0);
	//大于128页的就直接向堆中申请
	if (k > NPAGES - 1)
	{
		//
		void* ptr = SystemAlloc(k);
		Span* span = pageCache->_spanPool.New();
		span->_page_id = (PAGE_ID)ptr >> PAGE_SHIFT;
		span->_n = k;
		pageCache->_idSpanMap.set(span->_page_id, span);
		return span;
	}

	//先检查第K个桶里面有没有span
	if (! pageCache->_spanList[k].Empty())
	{
		Span* kSpan = pageCache->_spanList[k].PopFront();
		for (PAGE_ID i = 0; i < kSpan->_n; i++)
		{
			pageCache->_idSpanMap.set(kSpan->_page_id + i, kSpan);
		}
		return kSpan;
	}

	//第K个桶是空的 检查一下后面的桶里面有没有span
	for (size_t i = k + 1; i < NPAGES; i++)
	{
		if (!pageCache->_spanList[i].Empty())
		{
			Span* nspan = pageCache->_spanList[i].PopFront();
			Span* kspan= pageCache->_spanPool.New();

			//在nspan头部切一个k页下来
			//k页的span返给centralcache剩下的挂在对应的映射位置上
			kspan->_page_id = nspan->_page_id;
			kspan->_n = k;

			nspan->_page_id += k;
			nspan->_n -= k;
			pageCache->_spanList[nspan->_n].PushFront(nspan);

			//存储nSpan的首位页号跟nspan映射 
			//存储nSapn的首位页号跟nspan映射
			//方便page cache回收内存时,进行合并查找
			//_idSpanMap[nSpan->_pageId] = nSpan;
			pageCache->_idSpanMap.set(nspan->_page_id, nspan);
			//_idSpanMap[nSpan->_pageId + nSpan->_n - 1] = nSpan;
			pageCache->_idSpanMap.set(nspan->_page_id + nspan->_n - 1, nspan);
			//建立id和span的映射,方便central cache回收小块内存时,查找对应的span
			for (PAGE_ID i = 0; i < kspan->_n; ++i)
			{
				//_idSpanMap[kSpan->_pageId + i] = kSpan;
				pageCache->_idSpanMap.set(kspan->_page_id + i, kspan);
			}

			return kspan;
		}
	}
	//走到这个位置,后面没有大页的span
	//这时候就去找堆要一个128的span
	//Span* bigSpan = new Span;
	Span* bigSpan = pageCache->_spanPool.New();
	void* ptr = SystemAlloc(NPAGES - 1);
	bigSpan->_page_id = (PAGE_ID)ptr >> PAGE_SHIFT;
	bigSpan->_n = NPAGES - 1;

	pageCache->_spanList[bigSpan->_n].PushFront(bigSpan);

	return Newspan(k);
}

2.PageCache的核心框架实现

一样用的是单例模式–>饿汉模式 其优点在上一篇博客已经详细介绍

  • PageCache.h
#pragma once
#include "Common.h"
#include"ObjectPool.h"
#include"PageMap.h"
class PageCache
{
public:
	static PageCache* GetInstance()
	{
		return &_sInst;
	}
	Span* MapObjectToSpan(void* obj);
	//获取一个K页的span
	static Span* Newspan(size_t);
	std::mutex _pageMtx;
	void ReleaseSpanToPageCache(Span* span);
private:
	//禁构造和拷贝构造
	PageCache() {};
	PageCache(const PageCache& p) = delete;
	ObjectPool<Span> _spanPool;
	SpanList _spanList[NPAGES];
	PageCache& operator=(const PageCache&) = delete;
	static PageCache _sInst;

	TCMalloc_PageMap1<32 - PAGE_SHIFT> _idSpanMap;
};
 
  • PageCache.cpp
#define _CRT_SECURE_NO_WARNINGS
#include"PageCache.h"
PageCache PageCache::_sInst;
PageCache* pageCache = PageCache::GetInstance(); // 获取 PageCache 的单例对象

//获取一个K页的span
 Span* PageCache::Newspan(size_t k)
{
	assert(k > 0);
	//大于128页的就直接向堆中申请
	if (k > NPAGES - 1)
	{
		//
		void* ptr = SystemAlloc(k);
		Span* span = pageCache->_spanPool.New();
		span->_page_id = (PAGE_ID)ptr >> PAGE_SHIFT;
		span->_n = k;
		pageCache->_idSpanMap.set(span->_page_id, span);
		return span;
	}

	//先检查第K个桶里面有没有span
	if (! pageCache->_spanList[k].Empty())
	{
		Span* kSpan = pageCache->_spanList[k].PopFront();
		for (PAGE_ID i = 0; i < kSpan->_n; i++)
		{
			pageCache->_idSpanMap.set(kSpan->_page_id + i, kSpan);
		}
		return kSpan;
	}

	//第K个桶是空的 检查一下后面的桶里面有没有span
	for (size_t i = k + 1; i < NPAGES; i++)
	{
		if (!pageCache->_spanList[i].Empty())
		{
			Span* nspan = pageCache->_spanList[i].PopFront();
			Span* kspan= pageCache->_spanPool.New();

			//在nspan头部切一个k页下来
			//k页的span返给centralcache剩下的挂在对应的映射位置上
			kspan->_page_id = nspan->_page_id;
			kspan->_n = k;

			nspan->_page_id += k;
			nspan->_n -= k;
			pageCache->_spanList[nspan->_n].PushFront(nspan);

			//存储nSpan的首位页号跟nspan映射 
			//存储nSapn的首位页号跟nspan映射
			//方便page cache回收内存时,进行合并查找
			//_idSpanMap[nSpan->_pageId] = nSpan;
			pageCache->_idSpanMap.set(nspan->_page_id, nspan);
			//_idSpanMap[nSpan->_pageId + nSpan->_n - 1] = nSpan;
			pageCache->_idSpanMap.set(nspan->_page_id + nspan->_n - 1, nspan);
			//建立id和span的映射,方便central cache回收小块内存时,查找对应的span
			for (PAGE_ID i = 0; i < kspan->_n; ++i)
			{
				//_idSpanMap[kSpan->_pageId + i] = kSpan;
				pageCache->_idSpanMap.set(kspan->_page_id + i, kspan);
			}

			return kspan;
		}
	}
	//走到这个位置,后面没有大页的span
	//这时候就去找堆要一个128的span
	//Span* bigSpan = new Span;
	Span* bigSpan = pageCache->_spanPool.New();
	void* ptr = SystemAlloc(NPAGES - 1);
	bigSpan->_page_id = (PAGE_ID)ptr >> PAGE_SHIFT;
	bigSpan->_n = NPAGES - 1;

	pageCache->_spanList[bigSpan->_n].PushFront(bigSpan);

	return Newspan(k);
}
 Span* PageCache::MapObjectToSpan(void* obj)
 {
	 PAGE_ID id = ((PAGE_ID)obj >> PAGE_SHIFT);

	 //std::unique_lock<std::mutex> lock(_pageMtx);//出了作用域自动释放锁
	 //auto ret = _idSpanMap.find(id);
	 //if (ret != _idSpanMap.end())
	 //{
	 //	return ret->second;
	 //}
	 //else
	 //{
	 //	assert(false);
	 //	return nullptr;
	 //}
	 auto ret = (Span*)pageCache->_idSpanMap.get(id);
	 assert(ret != nullptr);
	 return ret;
 }

 void PageCache::ReleaseSpanToPageCache(Span* span)
 {
	 //大于128页的span,直接还给堆
	 if (span->_n > NPAGES - 1)
	 {
		 void* ptr = (void*)(span->_page_id << PAGE_SHIFT);
		 SystemFree(ptr);
		 //delete span;
		 _spanPool.Delete(span);
		 return;
	 }
	 //对span前后的页,尝试进行合并,缓解内存碎片问题(外碎片)

	 //对前后的页进行合并
	 while (1)
	 {
		 PAGE_ID prevId = span->_page_id - 1;
		 //auto ret = _idSpanMap.find(prevId);

		 前面的页号没有找到,不进行合并
		 //if (ret == _idSpanMap.end())
		 //{
		 //	break;
		 //}

		 auto ret = (Span*)_idSpanMap.get(prevId);
		 if (ret == nullptr)
		 {
			 break;
		 }
		 //前面相邻页的span在使用,不进行合并
		 Span* prevSpan = ret;
		 if (prevSpan->_isUse == true)
		 {
			 break;
		 }
		 //合并出超过128的span没办法管理,就不能继续合并
		 if (prevSpan->_n + span->_n > NPAGES - 1)
		 {
			 break;
		 }

		 //合并
		 span->_page_id = prevSpan->_page_id;
		 span->_n += prevSpan->_n;
		 _spanList[prevSpan->_n].Erase(prevSpan);
		 //delete prevSpan;
		 _spanPool.Delete(prevSpan);

	 }


	 //向后合并
	 while (1)
	 {
		 PAGE_ID nextId = span->_page_id + span->_n;
		 /*auto ret = _idSpanMap.find(nextId);
		 if (ret == _idSpanMap.end())
		 {
			 break;
		 }*/
		 auto ret = (Span*)_idSpanMap.get(nextId);
		 if (ret == nullptr)
		 {
			 break;
		 }
		 Span* nextSpan = ret;
		 if (nextSpan->_isUse == true)
		 {
			 break;
		 }
		 if (span->_n + nextSpan->_n > NPAGES - 1)
		 {
			 break;
		 }
		 span->_n += nextSpan->_n;

		 _spanList[nextSpan->_n].Erase(nextSpan);
		 //delete nextSpan;
		 _spanPool.Delete(nextSpan);
	 }
	 _spanList[span->_n].PushFront(span);
	 span->_isUse = false;
	 //_idSpanMap[span->_pageId] = span;
	 _idSpanMap.set(span->_page_id, span);
	 //_idSpanMap[span->_pageId + span->_n - 1] = span;
	 _idSpanMap.set(span->_page_id + span->_n - 1, span);
 }

这里着重介绍一下CentralCache向PageCache申请内存的过程:

  • GetOneSpan
Span* GetOneSpance(SpanList& list, size_t size)
{
	//查看一下当前spanlists是否span未分配的
	Span* it = list.Begin();
	while (it != list.End())
	{
		if (it->_freeList!=nullptr)
		{
			return it;
		}
		else
		{
			it = it->_next;
		}
	}

	//先把centralCache的桶解掉 ,这样如果其他的线程释放对象回来,不会阻塞

	list._mtx.unlock();

	//走到这里说明没有空闲的span了,再往下找PageCache要
	
	PageCache::GetInstance()->_pageMtx.lock(); //加锁 这是一个大锁

	Span* span = PageCache::Newspan(SizeClass::NumMovePage(size));

	span->_isUse = true;
	span->_objSize = size;
	PageCache::GetInstance()->_pageMtx.unlock();//到这一步程序就已经申请到一个span了

	//对span进行切分 此过程不需要加锁 因为其他的线程访问不到这个span

	//通过页号 计算出起始页的地址 add=_pageID<<PAGE_SHIFT
	//计算span的大块内存的起始地址 和大块内存的大小(字节数)

	char* start = (char*)(span->_page_id << PAGE_SHIFT);
	size_t bytes = span->_n << PAGE_SHIFT;
	char* end = start + bytes;

	//把大块内存切成自由链表 链接起来
	//这里使用尾插 因为尾插会保持内存空间的连续性 提高CPU的缓存利用率

	span->_freeList = start;
	start += size;
	void* tail = span->_freeList;
	int i = 1;
	while (start < end)
	{
		++i;
		NextObj(tail) = start;
		tail = NextObj(tail);
		start += size;
	}
	if (tail == nullptr)
	{
		int x = 0;
	}
	NextObj(tail) = nullptr;

	void* cur = span->_freeList;
	int koko=0;
	//条件断点 
	//类似死循环 可以让程序中断 程序会在运行的地方停下来

	while (cur)
	{
		cur = NextObj(cur);
		koko++;
	}
	if (koko != (bytes / 16))
	{
		int x = 0;
	}
	//这里切好span以后 需要把span挂到桶里面 同时加锁

	list._mtx.lock();
	list.PushFront(span);
	list._mtx.unlock();

	return span;
}

  1. 首先,代码通过 ‘list.Begin()’ 获取 ‘SpanList’ 中第一个 ‘Span’(链表头)的迭代器 ‘it’,然后遍历 ‘SpanList’。对于每个 ‘Span’,它检查 ‘_freeList’ 是否为空。如果 ‘_freeList’ 不为空,表示这个 ‘Span’ 中还有未分配的内存块,于是返回这个 ‘Span’,以便分配内存。如果 ‘_freeList’ 为空,表示这个 ‘Span’ 已分配完,于是继续遍历下一个 ‘Span’。

  2. 如果遍历完所有 ‘Span’ 后仍未找到可用的 ‘Span’,则说明没有足够的可用内存。此时,代码会解锁当前的 ‘list’,这个操作可以防止其他线程在等待可用内存时被阻塞。

  3. 然后,代码进入 PageCache 部分,通过 ‘PageCache::GetInstance()’ 获取 ‘PageCache’ 的单例对象,再对 ‘PageCache’ 进行加锁(‘PageCache::_pageMtx’)。这个锁用来保证只有一个线程能够执行下面的操作,以避免竞态条件。

  4. 代码通过 ‘PageCache::Newspan(SizeClass::NumMovePage(size))’ 创建一个新的 ‘Span’,这个 ‘Span’ 将用于进一步的内存分配。然后,标记这个 ‘Span’ 为正在使用(‘span->_isUse = true’)。

  5. 接下来,计算该 ‘Span’ 的大块内存的起始地址 ‘start’ 和大小 ‘bytes’。大块内存的大小由 ‘_n’ 字段乘以 ‘PAGE_SHIFT’ 决定。

  6. 接下来,将大块内存切分成小块,这些小块将成为自由链表的一部分。切分的过程是通过将内存地址链接起来来实现的。这里使用了尾插法,以确保内存块的连续性,提高 CPU 缓存的利用率。

  7. 将 ‘Span’ 的 ‘_freeList’ 指针指向自由链表的头部,然后将自由链表中每个内存块通过 ‘NextObj’ 连接起来。最后,将最后一个内存块的 ‘NextObj’ 指针设置为 ‘nullptr’,表示自由链表的结束。

  8. 最后,代码将新创建的 ‘Span’ 加锁并插入到 ‘list’ 的头部,以保证其被其他线程看到。然后解锁 ‘list’,以允许其他线程继续分配内存。

总的来说,这段代码用于从 ‘SpanList’ 或者 ‘PageCache’ 中获取一个 ‘Span’ 用于内存分配。如果 ‘SpanList’ 中没有可用的 ‘Span’,则会通过 ‘PageCache’ 创建新的 ‘Span’,再将其切分为小块内存,用于后续的对象分配。这个策略保证了内存分配的高效性,尤其在多线程环境下。

  • 把大块内存切分成自由链表
    00
  1. 首先,通过 ‘span->_page_id’ 计算出大块内存的起始地址 ‘start’。‘span->_page_id’ 存储了页号,而 ‘PAGE_SHIFT’ 是一个位移常量,用于将页号左移得到实际的内存地址。这样,‘start’ 指向了大块内存的起始地址
    000
    start+=size,往后挪了一个单位,然后依次链接

  2. 接下来,通过 ‘span->_n’ 计算出大块内存的总大小 ‘bytes’。同样,‘span->_n’ 表示了内存块的数量,乘以 ‘PAGE_SHIFT’ 得到总的字节数。

  3. 然后,定义一个指针 ‘end’,它指向大块内存的结束地址。这是通过将 ‘start’ 和 ‘bytes’ 相加得到的,确定了大块内存的范围。

  4. 设置 ‘span->_freeList’ 指向 ‘start’,表示自由链表的头部。这时链表还为空,所以 ‘start’ 就是链表的唯一节点。

  5. 接下来,使用 ‘while’ 循环遍历大块内存的地址范围,从 ‘start’ 开始,直到 ‘start’ 指向了 ‘end’ 为止。

  6. 在循环中,每次迭代都会将下一个内存块的地址(‘start’)链接到当前内存块的末尾(通过 ‘NextObj(tail) = start’)。‘tail’ 指向当前内存块的末尾,所以这里设置 ‘NextObj(tail)’ 会连接到下一个内存块。然后,‘tail’ 移动到下一个内存块的末尾。

  7. 同时,‘start’ 也向后移动一个内存块的大小(‘start += size’),以准备链接下一个内存块。

  8. 循环结束后,大块内存已经被切分成多个小块,并且这些小块通过 ‘NextObj’ 链接在一起,形成了一个自由链表。

总之,这段代码通过计算大块内存的起始地址和总大小,然后使用循环将这个大块内存切分成多个小块,并通过自由链表链接在一起。这个自由链表将用于对象的内存分配,保证了内存的高效使用。

00
点赞支持持续更新
在这里插入图片描述

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

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

相关文章

让你随时随地访问金蝶云星空企业版v8.0,内网穿透轻松实现远程办公!

文章目录 前言1. 金蝶云星空企业版v8.0安装下载1.1 登录金蝶官网下载安装包1.2 常见的安装下载问题 2. 金蝶云星空配置SQL Sever数据库2.1 创建数据管理中心2.2 创建完成后在服务器登录管理站点 3. 下载安装注册cpolar3.1 公网访问测试 4. 固定连接公网地址 前言 金蝶云星空专注…

【C++】二叉树进阶 -- 详解

一、二叉搜索树概念 二叉搜索树 又称二叉排序树&#xff0c;它或者是一棵空树&#xff0c;或者是具有以下性质的二叉树&#xff1a; 若它的左子树不为空&#xff0c;则左子树上所有节点的值都小于根节点的值 若它的右子树不为空&#xff0c;则右子树上所有节点的值都大于根节点…

API接口采集商品详情页面数据(H5端和PC端)item_get-获得淘宝商品详情

API接口是一种商业软件开发工具&#xff0c;可以帮助开发者实现业务需求。通过 API接口&#xff0c;开发人员可以快速搭建自己的应用&#xff0c;实现数据采集分析和处理&#xff0c;也可以通过这个接口完成与其它系统的集成与通信。电商API就是各大电商平台提供给开发者访问平…

深度学习中的不确定性综述

领域学者&#xff1a; http://www.gatsby.ucl.ac.uk/~balaji/ 论文标题&#xff1a; A Survey of Uncertainty in Deep Neural Networks 论文链接&#xff1a; https://arxiv.org/pdf/2107.03342.pdf 概要 在过去的十年中&#xff0c;神经网络几乎遍及所有科学领域&#x…

Mac电脑怎么在Dock窗口预览,Dock窗口预览工具DockView功能介绍

DockView是一款Mac电脑上的软件&#xff0c;它可以增强Dock的功能&#xff0c;让用户更方便地管理和切换应用程序。 DockView的主要功能是在 DockQ&#xff0c;栏上显示每个窗口的缩略图&#xff0c;并提供了一些相关的操作选项。当用户将鼠标悬停在Dock栏上的应用程序图标上时…

Centos磁盘问题小纪

场景说明 放个windows的图片镇楼&#xff0c;在给一个centos的来说明问题&#xff0c;咋了&#xff0c;好好的系统&#xff0c;啥也不能干了 来先上一波命令分析下问题 查看挂载 mount 重新挂载数据 mount -o remount, rw / 查看磁盘 df -h 查看分区挂载详情 rw读写权限 mount …

顶级玩家:一招搞定 App 自动化老大难问题

很多人在学习 App 自动化或者在项目中落地实践 App 自动化时&#xff0c;会发现编写的自动化脚本无缘无故的执行失败、不稳定。 而导致其问题很大原因是因为应用的各种弹窗&#xff08;升级弹窗、使用过程提示弹窗、评价弹窗等等&#xff09;&#xff0c;比如这样的&#xff1a…

vue3项目使用highlight.js插件实现了代码块

使用vue的都知道官网的代码块效果: 下面是我们实现了这个功能: 使用highlight.js就可以实现 官网: highlight.js 下载插件后,在main.ts文件中引入: 在文件中: 引入hljs,在onMounted回调中使用,希望使用什么主题就引入什么主题的css, 创建topic和pre这两个需要的, 因为上面j…

✔ ★【备战实习(面经+项目+算法)】 10.22学习时间表(算法刷题:4道)

✔ ★【备战实习&#xff08;面经项目算法&#xff09;】 坚持完成每天必做如何找到好工作1. 科学的学习方法&#xff08;专注&#xff01;效率&#xff01;记忆&#xff01;心流&#xff01;&#xff09;2. 每天认真完成必做项&#xff0c;踏实学习技术 认真完成每天必做&…

如何远程访问Linux本地WBO白板实现随时随地创作?

[TOC]如何远程访问Linux本地WBO白板实现随时随地创作&#xff1f;) 前言 WBO在线协作白板是一个自由和开源的在线协作白板&#xff0c;允许多个用户同时在一个虚拟的大型白板上画图。该白板对所有线上用户实时更新&#xff0c;并且状态始终保持。它可以用于许多不同的目的&am…

基于Python的淘宝商品API接口

淘宝API接口是一个丰富的接口库&#xff0c;提供了各种各样的接口供开发者使用。满足多语言开发。以下是一些常见的淘宝API接口及其代码示例。 1. 搜索商品API接口 该API提供了关键字搜索淘宝商品的功能&#xff0c;可以使用各种编程语言来访问。以下代码段展示了使用Python来…

程序包org.apache.ibatis.mapping不存在 符号找不到

找不到符号 符号: 类 Cursor和程序包org.apache.ibatis.mapping不存在 在idea中没有错误&#xff0c;但是在linux编辑时报了这两个错误&#xff0c;之前有遇见过符号找不到的问题&#xff0c; 当时的问题是编译的import xxx.xxx.xxx.* 识别不成功过&#xff0c;将*改为…

TX Text Control.NET For WPF 32.0 Crack

TX Text Control 支持VISUAL STUDIO 2022、.NET 5 和 .NET 6 支持 .NET WPF 应用程序的文档处理 将文档编辑、创建和 PDF 生成添加到您的 WPF 应用程序中。 视窗用户界面 功能齐全的文档编辑器 TX Text Control 是一款完全可编程的丰富编辑控件&#xff0c;它在专为 Visual Stu…

IS200TPR0S1CBB IS215VCMIH2C BJRL-20012-110001

IS200TPR0S1CBB IS215VCMIH2C BJRL-20012-110001 随着NVIDIA Jetson AGX Orin开发套件的发布&#xff0c;AAEON很高兴能够利用这种强大的模块上系统(SOM)为自己的产品线带来的诸多优势。与NVIDIA Jetson AGX Xavier具有相同的外形和引脚兼容性&#xff0c;但从32 TOPS提高到…

CVPR2023优秀论文 | AIGC伪造图像鉴别算法泛化性缺失问题分析

作者 | 搜索内容技术部 导读 深度伪造检测算法无法检出未知伪造算法生成的攻击数据。以往算法采取手动建模伪造特征的方式提升模型泛化性&#xff0c;然而这种方式限制了算法可行域&#xff0c;影响了模型泛化性进一步提升&#xff0c;同时这类方法参数量巨大&#xff0c;无法满…

代码随想录 Day26贪心算法01-上

目录 前言:贪心无套路 本质: 两个极端 贪心的小例子 贪心无套路!!! LeetCode T455 分发饼干 题目思路: 1.优先考虑胃口:大饼干喂饱大胃口 2.优先考虑饼干:小饼干先喂饱小胃口 前言:贪心无套路 本质: 局部最优去推导全局最优 两个极端 贪心算法的难度一般要么特别简单,要…

新增用户登录和资产登录通知功能,支持指定目录运行作业中心命令,JumpServer堡垒机v3.8.0发布

2023年10月23日&#xff0c;JumpServer开源堡垒机正式发布v3.8.0版本。在这一版本中&#xff0c;JumpServer在“用户登录”和“资产登录”这两个权限控制功能中&#xff0c;新增“通知”动作。目前其支持的动作包括拒绝、接受、审批以及通知四种动作&#xff0c;方便了管理员针…

栩栩如生,音色克隆,Bert-vits2文字转语音打造鬼畜视频实践(Python3.10)

诸公可知目前最牛逼的TTS免费开源项目是哪一个&#xff1f;没错&#xff0c;是Bert-vits2&#xff0c;没有之一。它是在本来已经极其强大的Vits项目中融入了Bert大模型&#xff0c;基本上解决了VITS的语气韵律问题&#xff0c;在效果非常出色的情况下训练的成本开销普通人也完全…

Qt音乐播放器

简介 使用QMediaPlayer和QMediaPlaylist制作的音乐播放器 编译环境 Qt5.6 MGW32 windows10 功能特性 GUI 功能 加载mp3文件&#xff0c;得到歌曲信息&#xff1b;打开文件夹加载或拖拽音乐文件加载滑动条关联播放进度、音量显示/隐藏歌曲列表&#xff0c;编辑歌曲列表&am…

【Proteus仿真】【STM32单片机】自动饲养控制系统

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真STM32单片机控制器&#xff0c;使用LCD1604显示模块、红外传感器、有害气体检测模块、PCF8591 ADC模块&#xff0c;蜂鸣器、DHT11温湿度、SG90舵机、风扇加热加湿等。 主要功能&a…