文章目录

  • 前言
  • 一、threadcache回收内存
  • 二、centralcache回收内存
  • 三、pagecache回收内存
  • 总结


前言

  Hello,我们继续乘胜追击
  本篇难度较大,大家要好好学一下


一、threadcache回收内存

  1. 当某个线程申请的对象不用了,可以将其释放给 thread cache ,然后 thread cache 将该对象插入到对应哈希桶的自由链表当中即可。
  2. 但是随着线程不断的释放,对应自由链表的长度也会越来越长,这些内存堆积在一个 thread cache 中就是一种浪费,我们应该将这些内存还给 central cache ,这样一来,这些内存对其他线程来说也是可申请的,因此当 thread cache 某个桶当中的自由链表太长时我们可以进行一些处理。
  3. 如果 thread cache 某个桶当中自由链表的长度超过它一次批量向 central cache 申请的对象个数,那么此时我们就要把该自由链表当中的这些对象还给 central cache 。
// 释放内存对象
void ThreadCache::Deallocate(void* ptr, size_t size)
{assert(ptr);assert(size <= MAX_BYTES);// 找出对应的自由链表桶将对象插入size_t index = SizeClass::Index(size);_freeLists[index].Push(ptr);// 当自由链表长度大于一次批量申请的对象个数时就开始还一段list给central cacheif (_freeLists[index].Size() >= _freeLists[index].MaxSize()){ListTooLong(_freeLists[index], size);}
}

  当自由链表的长度大于一次批量申请的对象时,我们具体的做法就是,从该自由链表中取出一次批量个数的对象,然后将取出的这些对象还给 central cache 中对应的 span 即可

// 释放对象时,链表过长时,回收内存回到中心缓存
void ThreadCache::ListTooLong(FreeList& list, size_t size)
{void* start = nullptr;void* end = nullptr;// 从list取出批量个对象list.PopRange(start, end, list.MaxSize());// 将取出的对象还给central cache对应的spanCentralCache::GetInstance()->ReleaseListToSpans(start, size);
}

  从上述代码可以看出, FreeList类 需要支持用 Size函数 获取自由链表中对象的个数,还需要支持用 PopRange 函数从自由链表中取出指定个数的对象。因此我们需要给 FreeList类 增加一个对应的 PopRange函数 ,然后再增加一个 _size 成员变量,该成员变量用于记录当前自由链表中对象的个数,当我们向自由链表插入或删除对象时,都应该更新 _size 的值。

// 管理切分好的小对象的自由链表
class FreeList
{
public:// 将释放的对象头插到自由链表void Push(void* obj){assert(obj);//头插NextObj(obj) = _freeList;_freeList = obj;_size++;}// 从自由链表头部获取一个对象void* Pop(){assert(_freeList);//头删void* obj = _freeList;_freeList = NextObj(_freeList);_size--;return obj;}// 插入一段范围的对象到自由链表void PushRange(void* start, void* end, size_t n){assert(start);assert(end);//头插NextObj(end) = _freeList;_freeList = start;					_size += n;}// 从自由链表获取一段范围的对象void PopRange(void*& start, void*& end, size_t n){assert(n <= _size);//头删start = _freeList;end = start;for (size_t i = 0; i < n - 1;i++){end = NextObj(end);}_freeList = NextObj(end); 	// 自由链表指向end的下一个对象NextObj(end) = nullptr; 	// 取出的一段链表的表尾置空_size -= n;}bool Empty(){return _freeList == nullptr;}size_t& MaxSize(){return _maxSize;}size_t Size(){return _size;}
private:void* _freeList = nullptr; // 自由链表size_t _maxSize = 1;size_t _size = 0;
};

在这里插入图片描述
  对于 FreeList类 当中的 PushRange 成员函数,我们最好也像 PopRange 一样给它增加一个参数,表示插入对象的个数,不然我们这时还需要通过遍历统计插入对象的个数

在这里插入图片描述
  因此之前在调用 PushRange 的地方就需要修改一下,而我们实际就在一个地方调用过 PushRange 函数,并且此时插入对象的个数也是很容易知道的。当时 thread cache 从 central cache 获取了 actualNum 个对象,将其中的一个返回给了申请对象的线程,剩下的 actualNum - 1 个挂到了 thread cache 对应的桶当中,所以这里插入对象的个数就是 actualNum - 1。

// 从中心缓存获取对象
void* ThreadCache::FetchFromCentralCache(size_t index, size_t size)
{size_t batchNum = min(_freeLists[index].MaxSize(), SizeClass::NumMoveSize(size));if (_freeLists[index].MaxSize() == batchNum){_freeLists[index].MaxSize() += 1;}void* start = nullptr;void* end = nullptr;size_t actualNum = CentralCache::GetInstance()->FetchRangeObj(start, end, batchNum, size);assert(actualNum >= 1); if (actualNum == 1){assert(start == end);return start;}else{_freeLists[index].PushRange(Next(start),end, actualNum - 1); // 修改部分return start;}
}
  1. 当 thread cache 的某个自由链表过长时,我们实际就是把这个自由链表当中全部的对象都还给 central cache 了,但这里在设计 PopRange 接口时还是设计的是取出指定个数的对象,因为在某些情况下当自由链表过长时,我们可能并不一定想把链表中全部的对象都取出来还给 central cache ,这样设计就是为了增加代码的可修改性。

  2. 当我们判断 thread cache 是否应该还对象给 central cache 时,还可以综合考虑每个 thread cache 整体的大小。比如当某个 thread cache 的总占用大小超过一定阈值时,我们就将该 thread cache 当中的对象还一些给 central cache ,这样就尽量避免了某个线程的 thread cache 占用太多的内存。对于这一点,在 tcmalloc 当中就是考虑到了的。(但是对我们来说还是太难了,至少我实在是不会)

二、centralcache回收内存

  当 thread cache 中某个自由链表太长时,会将自由链表当中的这些对象还给 central cache 中的 span

  还给 central cache 的这些对象不一定都是属于同一个 span 的。 central cache 中的每个哈希桶当中可能都不止一个 span ,因此当我们计算出还回来的对象应该还给 central cache 的哪一个桶后,还需要知道这些对象到底应该还给这个桶当中的哪一个 span

至于说为什么还给 central cache 的这些对象不一定都是属于同一个 span 的,答案在这里
在这里插入图片描述

至于说为什么需要区分 Span,原因在这里

在这里插入图片描述

那么如何根据对象的地址得到对象所在的页号?

  某个页当中的所有地址除以页的大小都等该页的页号。比如我们这里假设一页的大小是100,那么地址0 ~ 99都属于第0页,它们除以100都等于0,而地址100 ~ 199都属于第1页,它们除以100都等于1

如何找到一个对象对应的span?

  虽然我们现在可以通过对象的地址得到其所在的页号,但是我们还是不能知道这个对象到底属于哪一个 span 。因为一个 span 管理的可能是多个页

在这里插入图片描述

  为了解决这个问题,我们可以建立 页号 和 span 之间的映射。由于这个映射关系在 page cache 进行 span 的合并时也需要用到,因此我们直接将其存放到 page cache 里面。这时我们就需要在 PageCache类 当中添加一个映射关系了,这里可以用 C++ 当中的 unordered_map 进行实现,并且添加一个函数接口,用于让 central cache 获取这里的映射关系。

PageCache类当中新增的成员变量及其成员函数

// 单例模式
class PageCache
{
public:// 获取从对象到span的映射Span* MapObjectToSpan(void* obj);
private:std::unordered_map<PAGE_ID, Span*> _idSpanMap;
};

  每当 page cache 分配 span 给 central cache 时,都需要记录一下 页号 和 span 之间的映射关系。此后当 thread cache 还对象给 central cache 时,才知道应该具体还给哪一个 span

  因此当 central cache 在调用 NewSpan 接口向 page cache 申请 k页 的 span 时, page cache 在返回这个 k页 的 span 给 central cache 之前,应该建立这 k个页号 与该 span 之间的映射关系

// 获取一个K页的span(加映射版本)
Span* PageCache::NewSpan(size_t k)
{assert(k > 0 && k < NPAGES); // 保证在桶的范围内// 1.检查第k个桶里面有没有Span,有则返回if (!_spanLists[k].Empty()){Span* kSpan = _spanLists[k].PopFront();// 建立页号与span的映射,方便central cache回收小块内存查找对应的spanfor (PAGE_ID i = 0; i < kSpan->_n; i++){_idSpanMap[kSpan->_pageID + i] = kSpan;}return kSpan;}// 2.检查一下后面的桶里面有没有span,有则将其切分for (size_t i = k + 1; i < NPAGES; i++){if (!_spanLists[i].Empty()){Span* nSpan = _spanLists[i].PopFront();Span* kSpan = new Span;// 在nSpan的头部切一个k页下来kSpan->_pageID = nSpan->_pageID;kSpan->_n = k;// 更新nSpan位置nSpan->_pageID += k;nSpan->_n -= k;// 将剩下的挂到对应映射的位置_spanLists[nSpan->_n].PushFront(nSpan);for (PAGE_ID i = 0; i < kSpan->_n; i++){_idSpanMap[kSpan->_pageID + i] = kSpan;}return kSpan;}}// 3.没有大页的span,找堆申请128页的spanSpan* bigSpan = new Span;void* ptr = SystemAlloc(NPAGES - 1); // 申请128页内存bigSpan->_pageID = (PAGE_ID)ptr >> PAGE_SHIFT;bigSpan->_n = NPAGES - 1; // 页数// 将128页span挂接到128号桶上_spanLists[bigSpan->_n].PushFront(bigSpan);// 递归调用自己(避免代码重复)return NewSpan(k);
}

  此时我们就可以通过对象的地址找到该对象对应的 span 了,直接将该对象的地址除以页的大小得到页号,然后在 unordered_map 当中找到其对应的 span 即可

// 获取从对象到span的映射
Span* PageCache::MapObjectToSpan(void* obj)
{PAGE_ID id = (PAGE_ID)obj >> PAGE_SHIFT; // 计算页号auto ret = _idSpanMap.find(id);if (ret != _idSpanMap.end()){return ret->second;}else{assert(false);return nullptr;}
}

  注意:当我们要通过某个页号查找其对应的 span 时,该页号与其 span 之间的映射一定是建立过的,如果此时我们没有在 unordered_map 当中找到,则说明我们之前的代码逻辑有问题,因此当没有找到对应的 span 时可以直接用断言结束程序,以表明程序逻辑出错

  这时当 thread cache 还对象给 central cache 时,就可以依次遍历这些对象,将这些对象插入到其对应 span 的自由链表当中,并且及时更新该 span 的 _useCount 计数即可

  在 thread cache 还对象给 central cache 的过程中,如果 central cache 中某个 span 的 _useCount 减到 0 时,说明这个 span 分配出去的对象全部都还回来了,那么此时就可以将这个 span 再进一步还给 page cache

//将一定数量的对象还给对应的span
void CentralCache::ReleaseListToSpans(void* start, size_t size)
{size_t index = SizeClass::Index(size);_spanLists[index]._mtx.lock(); // 加锁while (start){void* next = NextObj(start); // 记录下一个Span* span = PageCache::GetInstance()->MapObjectToSpan(start);// 将对象头插到span的自由链表NextObj(start) = span->_freeList;span->_freeList = start;span->_useCount--; // 更新被分配给thread cache的计数if (span->_useCount == 0) // 说明这个span分配出去的对象全部都回来了{// 此时这个span就可以再回收给page cache,page cache可以再尝试去做前后页的合并_spanLists[index].Erase(span);span->_freeList = nullptr; // 自由链表置空span->_next = nullptr;span->_prev = nullptr;// 释放span给page cache时,使用page cache的锁就可以了,这时把桶锁解掉_spanLists[index]._mtx.unlock(); // 解桶锁PageCache::GetInstance()->_pageMtx.lock(); // 加大锁PageCache::GetInstance()->ReleaseSpanToPageCache(span);PageCache::GetInstance()->_pageMtx.unlock(); // 解大锁_spanLists[index]._mtx.lock(); // 加桶锁}start = next;}_spanLists[index]._mtx.unlock(); // 解锁
}

在这里插入图片描述
  如果要把某个 span 还给 page cache ,我们需要先将这个 span 从 central cache 对应的双链表中移除,然后再将该 span 的自由链表置空,因为 page cache 中的 span 是不需要切分成一个个的小对象的,以及该 span 的前后指针也都应该置空,因为之后要将其插入到 page cache 对应的双链表中。但 span 当中记录的起始页号以及它管理的页数是不能清除的,否则对应内存块就找不到了

  在 central cache 还 span 给 page cache 时也存在锁的问题,此时需要先将 central cache 中对应的桶锁解掉,然后再加上 page cache 的大锁之后才能进入 page cache 进行相关操作,当处理完毕回到 central cache 时,除了将 page cache 的大锁解掉,还需要立刻加上 central cache 对应的桶锁,然后将还未还完对象继续还给 central cache 中对应的 span

三、pagecache回收内存

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

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

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

  当我们通过页号找到其对应的 span 时,这个 span 此时可能挂在 page cache ,也可能挂在 central cache 。而在合并时我们只能合并挂在 page cache 的 span ,因为挂在 central cache 的 span 当中的对象正在被其他线程使用。
  可是我们不能通过 span 结构当中的 _useCount 成员,来判断某个 span 到底是在 central cache 还是在 page cache 。因为当 central cache 刚向 page cache 申请到一个 span 时,这个 span 的 _useCount 就是等于 0 的,这时可能当我们正在对该 span 进行切分的时候, page cache 就把这个 span 拿去进行合并了,这显然是不合理的。
  基于上面的分析,我们可以在 span 结构中再增加一个 _isUse 成员,用于标记这个 span 是否正在被使用,而当一个 span 结构被创建时我们默认该 span 是没有被使用的。

// 管理多个连续页大块内存跨度结构
struct Span
{PAGE_ID _pageID = 0;	   // 大块内存起始页的页号size_t _n = 0;			   // 页的数量Span* _next = nullptr;     // 双向链表结构Span* _prev = nullptr;size_t _useCount = 0;      // 切好的小内存块,被分配给thread cache的计数void* _freeList = nullptr; // 切好的小块内存的自由链表bool _isUse = false;	   // 是否在被使用
};

  当 central cache 向 page cache 申请到一个 span 时 (CentralCache::GetOneSpan函数) ,需要立即将该 span 的 _isUse 改为 true

// 获取一个非空的span
Span* CentralCache::GetOneSpan(SpanList& list, size_t size)
{//...Span* span = PageCache::GetInstance()->NewSpan(SizeClass::NumMovePage(size));span->_isUse = true; // 设置为truePageCache::GetInstance()->_pageMtx.unlock();//...
}

  当 central cache 将某个 span 还给 page cache 时 (PageCache::ReleaseSpanToPageCache函数) ,也就需要将该 span 的 _isUse 改成 false

// 释放空闲的span回到PageCache,并合并相邻的span
void PageCache::ReleaseSpanToPageCache(Span* span)
{//...span->_isUse = false;
}

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

  与 central cache 中的 span 不同的是,在 page cache 中,只需建立一个 span 的首尾页号与该 span 之间的映射关系。因为当一个 span 在尝试进行合并时,如果是往前合并,那么只需要通过一个 span 的尾页找到这个 span ,如果是向后合并,那么只需要通过一个 span 的首页找到这个 span 。也就是说,在进行合并时我们只需要用到 span 与其首尾页之间的映射关系就够了。

  因此当我们申请 k页 的 span 时,如果是将 n页 的 span 切成了一个 k页 的 span 和一个 n-k页 的 span ,我们除了需要建立 k页 span 中每个页与该 span 之间的映射关系之外,还需要建立剩下的 n-k页 的 span 与其首尾页之间的映射关系。

// 获取一个K页的span(加映射版本)
Span* PageCache::NewSpan(size_t k)
{assert(k > 0 && k < NPAGES); // 保证在桶的范围内// 1.检查第k个桶里面有没有Span,有则返回if (!_spanLists[k].Empty()){Span* kSpan = _spanLists[k].PopFront();// 建立页号与span的映射,方便central cache回收小块内存查找对应的spanfor (PAGE_ID i = 0; i < kSpan->_n; i++){_idSpanMap[kSpan->_pageID + i] = kSpan;}return kSpan;}// 2.检查一下后面的桶里面有没有span,有则将其切分for (size_t i = k + 1; i < NPAGES; i++){if (!_spanLists[i].Empty()){Span* nSpan = _spanLists[i].PopFront();Span* kSpan = new Span;// 在nSpan的头部切一个k页下来kSpan->_pageID = nSpan->_pageID;kSpan->_n = k;// 更新nSpan位置nSpan->_pageID += k;nSpan->_n -= k;// 将剩下的挂到对应映射的位置_spanLists[nSpan->_n].PushFront(nSpan);for (PAGE_ID i = 0; i < kSpan->_n; i++){_idSpanMap[kSpan->_pageID + i] = kSpan;}return kSpan;}}// 3.没有大页的span,找堆申请128页的spanSpan* bigSpan = new Span;void* ptr = SystemAlloc(NPAGES - 1); // 申请128页内存bigSpan->_pageID = (PAGE_ID)ptr >> PAGE_SHIFT;bigSpan->_n = NPAGES - 1; // 页数// 将128页span挂接到128号桶上_spanLists[bigSpan->_n].PushFront(bigSpan);// 递归调用自己(避免代码重复)return NewSpan(k);
}

总结

  本篇难度相当大,后面可能还会有类似难度的文章,大家要好好消化!!

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

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

相关文章

2438. 二的幂数组中查询范围内的乘积

2438. 二的幂数组中查询范围内的乘积 初始理解题目 首先&#xff0c;我们需要清楚地理解题目在说什么。题目给出一个正整数 n&#xff0c;要求我们构造一个数组 powers&#xff0c;这个数组满足以下条件&#xff1a; 元素性质​&#xff1a;数组中的每个元素都是 2 的幂。即…

【PyTorch学习笔记 - 01】 Tensors(张量)

最近项目需要优化一下目标检测网络&#xff0c;在这个过程中发现还是得增加对框架底层的掌握才可行。于是准备对pytorch的一些基本概念做一些再理解。参考PyTorch的wiki&#xff0c;对自己的学习过程做个记录。 Tensors 是一种特殊的数据结构&#xff0c;与数组和矩阵非常相似…

【C/C++】(struct test*)0->b 讲解

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、什么是结构体成员的偏移量&#xff1f; 二、为什么需要计算偏移量&#xff1f; 三、如何计算偏移量&#xff1f; 四、总结 一、什么是结构体成员的偏移量&#…

使用Pytest进行接口自动化测试(三)

&#xff08;一&#xff09;YAML 之前在项目中&#xff0c;我们也是用过YAML来做配置文件&#xff0c;他用于以人类可读的形式存储信息&#xff0c; 特点: 一种简易的可读语言&#xff0c;用于人和计算机交换数据 通常用来存储配置信息 跟python类似&…

算法训练营day46 647. 回文子串、516.最长回文子序列、动态规划总结篇

今天是动态规划的最后一篇内容了&#xff0c;本篇主要是针对回文字符串这种“与众不同”的递推规律来进行讲解 647. 回文子串 统计并返回这个字符串中 回文子串 的数目 暴力解法 两层for循环&#xff0c;遍历区间起始位置和终止位置&#xff0c;然后还需要一层遍历判断这个区…

Qt界面优化

1.QSS在网页前端开发领域中&#xff0c;CSS 是一个至关重要的部分&#xff0c;描述了一个网页的 “样式”&#xff0c;从而起到对网页美化的作用。所谓样式&#xff0c;包括不限于大小、位置、颜色、背景、间距、字体等等。网页开发作为 GUI 的典型代表&#xff0c;也对于其他客…

week1+2+3

408 计组 1.基本组成2.数据的表示和运算定点数&#xff1a;把数字分为定点整数和定点小数分开存储 浮点数&#xff1a;用科学计数法存储 原码 -全部取反-> 反码 反码 1->补码 补码 -符号位取反->移码带余除法&#xff1a;设x,m∈Z&#xff0c;m>0则存在唯一的整数q…

java8中javafx包缺少报错

今天拉取一个jdk1.8的项目里面有一个代码用到了javafx&#xff0c;这个我记得是jdk中的包&#xff0c;正常不应该报错的。然后发现jdk中还真没有&#xff0c;查了一下是因为版本问题。 Java 8 及之前&#xff1a;Oracle JDK 自带 JavaFX&#xff0c;OpenJDK 通常不包含Java 9 …

day072-代码检查工具-Sonar与maven私服-Nexus

文章目录0. 老男孩思想-选对池塘钓美人鱼1. 代码回滚方案2. SonarQube2.1 代码检查工具2.2 部署sonarqube2.2.1 软件要求2.2.2 安装软件2.2.3 启动sonar2.2.4 部署插件2.3 sonar检查java代码2.3.1 创建sona项目2.3.2 分析java代码2.3.3 Jenkins结合sonar检查代码2.4 sonar检查非…

【前端基础】15、列表元素、表格元素、表单元素(注:极其粗略的记载。)

一、列表元素 1、什么是列表元素2、有序列表&#xff08;ol、li&#xff09; ol有序列表 直接子元素只能是li。 li列表中的每一项。3、无序列表&#xff08;ul、li&#xff09; ol无序列表 直接子元素只能是li。 li列表中的每一项。4、定义列表&#xff08;dl、dt、dd&#xff…

IRFBG30PBF Vishay威世MOSFET场效应管

IRFBG30PBF Vishay威世&#xff1a;超快MOSFET 场效应管一、产品定位IRFBG30PBF 是Vishay威世推出的600V/30A N沟道功率MOSFET&#xff0c;采用第五代TrenchFET技术&#xff0c;专为开关电源、电机驱动、新能源逆变器等高功率场景设计。以85mΩ超低导通电阻和超快反向恢复&…

【07-AGI的讨论】

AI ANI&#xff1a;artificial narrow intelligence; 如 智能音箱&#xff1b;自动驾驶汽车&#xff0c;网络搜索&#xff0c;其他用于专业特定事项的工具&#xff1b; AGI&#xff1a;artificial general intelligence; building AI systems that could do anything a typical…

[激光原理与应用-225]:机械 - 3D图与2D图各自的作用

在机械设计与加工领域&#xff0c;3D图和2D图是两种核心的工程表达方式&#xff0c;它们在产品设计、制造、装配及维护等环节中扮演不同角色&#xff0c;具有互补性。以下是它们各自的作用及具体应用场景的详细解析&#xff1a;一、3D图的作用1. 直观展示产品全貌三维可视化&am…

【从零开始java学习|第一篇】java中的名词概念(JDK、JVM、JRE等等)

目录 一、核心运行环境三要素&#xff08;JVM/JRE/JDK&#xff09; 二、常用开发指令&#xff08;JDK 自带工具&#xff09; 三、一些其他概念 四、总结核心逻辑链 要入门 Java&#xff0c;理解核心概念之间的关系是基础。以下是 Java 中最核心的基础概念、工具及相关名词的…

UVa12345 Dynamic len(set(a[L:R]))

[TOC](UVa12345 Dynamic len(set(a[L:R]))) 题目链接 UVA - 12345 Dynamic len(set(a[L:R])) 题意 有编号从 0 到 n−1 的 n 个数&#xff0c;有两种操作&#xff1a; Q L R 询问编号 L 到编号 R−1 的数中有多少个不同的数字。M X Y 将编号为 X 的数字改为 Y。 你的任务就是…

[Ubuntu] VNC连接Linux云服务器 | 实现GNOME图形化

将桌面环境修改为 GNOME 并通过 VNC 远程访问的步骤 & TightVNC 的安装与配置说明&#xff1a;1. 安装 GNOME 桌面环境 sudo apt update sudo apt install ubuntu-gnome-desktop -y2. 安装 TightVNC 服务器 sudo apt install tightvncserver -y3. 初始化 VNC Server 并设置…

进程、网络通信方法

一、进程间通信(IPC)方法 适用于同一台主机上的进程间数据交换。 管道(Pipe) 匿名管道:单向通信,仅用于父子进程。 命名管道(FIFO):通过文件系统路径访问,支持无亲缘关系进程。 消息队列(Message Queue) 结构化消息(类型+数据),按类型读取,支持异步通信。…

[激光原理与应用-241]:设计 - 266n皮秒深紫外激光器,哪些因素影响激光器紫外光的输出功率?

一、短期稳定性266nm皮秒深紫外激光器紫外光输出功率的稳定性受非线性晶体性能、光学系统设计、热管理效果、重复频率与脉冲能量匹配度、环境干扰控制等因素影响&#xff0c;具体分析如下&#xff1a;1. 非线性晶体性能晶体选择与状态&#xff1a;BBO&#xff08;偏硼酸钡&…

Django配置sqllite之外的数据库

当连接到其他数据库后端时&#xff0c;如 MariaDB、MySQL、Oracle 或 PostgreSQL&#xff0c;将需要额外的连接参数。请参阅下面的 ENGINE 配置&#xff0c;了解如何指定其他数据库类型。这个例子是针对 PostgreSQL&#xff1a; 在django项目的settings.py文件里&#xff0c;关…

银河通用招人形机器人强化学习算法工程师了

人形强化学习算法工程师&#xff08;26届&#xff09;&#xff08;岗位信息已通过jobleap.cn授权&#xff0c;可在csdn发布&#xff09;银河通用机器人 北京收录时间&#xff1a; 2025年08月11日职位描述1. 研发基于深度强化学习的足式机器人运动控制算法&#xff0c;提升机器…