缓存置换:用c++实现最不经常使用(LFU)算法

在探讨缓存置换算法时,我们曾详细解读过LRU(Least Recently Used)算法,它凭借 “最近最少使用” 的策略在缓存管理领域大放异彩。今天,让我们将目光聚焦于另一种重要的缓存置换算法 ——LFU(Least Frequently Used 最不经常使用),深入探究它的原理、实现方式、应用场景以及与 LRU 的差异。

一、LFU 算法核心原理

LFU 算法的核心思想是根据数据的访问频率来决定淘汰对象。与 LRU 依据数据最近的访问时间不同,LFU 更关注数据被访问的频繁程度。它认为,在过去一段时间内访问次数最少的数据,在未来被访问的可能性也相对较低,因此当缓存空间不足时,优先淘汰这类数据。

想象一下图书馆的藏书管理,有些书籍经常被借阅,而有些则鲜有人问津。LFU 算法就如同图书馆管理员,会定期清理那些借阅次数最少的书籍,为新购入的书籍腾出空间。这样,图书馆的书架上始终保留着最受欢迎、被借阅可能性最高的书籍,提高了读者找到所需书籍的效率。在缓存管理中,LFU 算法通过记录每个数据的访问次数,在缓存满时淘汰访问次数最少的数据,以此来维持缓存的高效运行。

二、LFU 算法的数据结构实现

实现 LFU 算法需要借助合适的数据结构,我们的代码实现使用的是哈希表和多个双向链表的组合。

  1. 哈希表:哈希表则用于快速定位数据。它以数据的键作为索引,存储对应数据在双向链表中的节点指针。这样,在查找数据时,通过哈希表可以在 O(1) 的时间复杂度内找到对应的数据节点,然后根据节点指针在双向链表中进行操作。例如,在一个基于键值对存储的内存缓存中,通过哈希表可以快速找到特定键对应的数据,提高缓存的查询效率。

不清楚哈希表的可以看我以前的文章c++ 手写STL篇(三)实现hashtable

  1. 双向链表:在缓存管理中,双向链表常被用于维护数据的访问顺序,链表里的每个节点都代表着一个缓存数据。LRU 和 LFU 算法都用到了双向链表,但使用方式有所不同。

    在 LRU 算法里,只有一个双向链表。这个链表有个很清晰的规则:头部的节点代表着最近使用的数据,就好像是你刚刚才翻阅过的资料,被放在了最显眼的位置;而尾部的节点则代表最近最少使用的数据,类似于你很久都没碰过的旧文件,被放在了最角落。

    LFU 算法就不太一样了。在 LFU 中存在多个双向链表,那些访问频次相同的数据节点会被串联在同一个双向链表中。比如说,有一些数据都只被访问过 1 次,它们就会被放在一个双向链表;另外一些都被访问过 3 次的数据,则会在另一个双向链表。为了能快速找到不同访问频次对应的双向链表,LFU 通过哈希表将访问频次和对应的双向链表建立映射关系,这样就能很方便地管理和查找数据了。双向链表的优势在于,插入和删除操作的时间复杂度都是 O(1) ,这对于频繁的缓存操作来说非常高效。例如,在一个频繁读写的数据库缓存中,数据的插入和删除操作会经常发生,双向链表的这种特性可以保证缓存操作的快速执行。

不清楚双向链表的可以看这篇文章 c++ 手写STL篇(二) 用双向链表实现list核心功能_c++ 二维链表手写-CSDN博客

通过哈希表和双向链表的协同工作,LFU 算法能够高效地管理数据的访问次数,并快速找到访问次数最少的数据进行淘汰。

三、LFU 算法的操作流程

  1. 查询操作:当进行查询操作时,首先在哈希表中查找数据的键。如果找到,说明数据在缓存中,将对应数据的访问次数加 1,并调整双向链表以保持堆的有序性(因为访问次数发生了变化)。如果未找到,则说明数据不在缓存中,需要从其他数据源获取数据(如磁盘),然后将数据插入到缓存中。

  2. 插入操作:插入操作分为两种情况。若缓存未满,直接将数据插入到哈希表和双向链表中,并将其访问次数初始化为 1。若缓存已满,则先从访问频次最小的双向链表中取出元素,将其从哈希表和双向链表中删除,然后再将新数据插入到哈希表和双向链表中,并将其访问次数设置为 1。

  3. 删除操作:删除操作相对简单,在哈希表中查找要删除数据的键,找到后从哈希表和双向链表中删除对应的元素即可。

四、LFU 算法的应用场景

  1. CDN 缓存优化:内容分发网络(CDN)的主要任务是将内容缓存到离用户更近的节点,以加快用户的访问速度。在 CDN 中,LFU 算法可用于管理缓存内容。例如,在视频网站的 CDN 系统中,视频片段会被缓存到各个节点。随着用户观看不同的视频,CDN 节点的缓存空间会逐渐被占满。LFU 算法会根据视频片段的访问次数,淘汰那些访问次数最少的片段,为新的热门视频片段腾出空间。这样,用户在访问视频时,CDN 节点能够更快地提供热门视频内容,提升用户体验。(在流媒体中cdn是成本最高环节,因此如何节省资源很重要)

  2. 搜索引擎缓存管理:搜索引擎在处理大量的搜索请求时,会将经常查询的关键词及其搜索结果缓存起来。LFU 算法可以帮助搜索引擎决定淘汰哪些缓存内容。当缓存空间不足时,优先淘汰那些搜索次数最少的关键词及其结果。比如,在某一时间段内,一些热门话题的搜索频率很高,而一些生僻关键词的搜索次数较少。LFU 算法会将这些生僻关键词的缓存结果淘汰,保证缓存中保留的是更有可能被再次搜索的热门关键词及其结果,提高搜索引擎的响应速度。

  3. 游戏资源缓存策略:在游戏运行过程中,会加载大量的资源,如纹理、模型等。为了提高游戏的加载速度和运行效率,游戏会将这些资源缓存到内存中。LFU 算法可以根据资源的使用频率,淘汰那些使用次数最少的资源。例如,在一款开放世界游戏中,玩家在某个区域频繁活动,该区域的游戏资源访问频率较高,而一些偏远区域的资源访问次数较少。LFU 算法会优先淘汰这些偏远区域的资源,为当前区域更需要的资源腾出空间,确保游戏能够流畅运行。

五、LFU 与 LRU 的对比分析

  1. 原理差异:LRU 基于数据的访问时间,优先淘汰最近最少使用的数据;而 LFU 基于数据的访问频率,优先淘汰访问次数最少的数据。这意味着 LRU 更注重数据的时效性,而 LFU 更关注数据的使用频繁程度。

  2. 适用场景差异:LRU 适用于数据访问具有时间局部性的场景,即近期访问过的数据在未来一段时间内再次被访问的概率较高。例如,在浏览器缓存中,用户通常会在短时间内回访之前浏览过的网页,LRU 算法能够很好地适应这种场景。而 LFU 适用于数据访问频率相对稳定的场景,对于那些访问频率变化较大的数据,LFU 可能无法及时适应。比如在电商平台中,商品的热门程度可能会随着时间和促销活动发生较大变化,此时 LFU 算法可能不太适用。

  3. 实现复杂度差异:LFU 算法由于需要记录数据的访问次数并维护一个有序的数据结构,其实现复杂度相对较高。而 LRU 算法通常借助双向链表和哈希表即可实现,相对来说实现较为简单。

六、LFU 算法的优缺点

  1. 优点:能更准确地反映数据的使用频繁程度,在数据访问频率稳定的场景下,能够有效提高缓存命中率,减少数据的重复读取。例如在一些数据访问模式较为固定的企业级应用中,LFU 算法可以充分发挥其优势,提升系统性能。

  2. 缺点:需要额外的空间来记录数据的访问次数,并且维护有序数据结构的操作也会增加时间复杂度;对于过时的热点数据,因为其访问频次高,难以被淘汰;在高并发环境下,频繁更新访问次数和调整有序数据结构可能会带来性能瓶颈;对于刚加入缓存的数据可能应为频次很低而被快速淘汰,即便这项是近期热点数据(短期热点数据无法及时缓存)。

七、用c++实现LFU算法

LFU 缓存置换算法是一种经典且实用的算法,在众多领域都有广泛的应用。通过深入理解其原理、数据结构选择、操作流程以及应用场景,我们可以更好地将其应用到实际项目中,提升系统的性能。接下来,我们将通过手撕代码的方式,详细展示如何实现一个 LFU 缓存。

代码参考自:https://github.com/youngyangyang04/KamaCache

接口类,对缓存置换算法设计统一接口实现隔离 :

#ifndef CACHEPOLICY_H
#define CACHEPOLICY_Htemplate<typename Key, typename Value>
class CachePolicy
{
public:virtual ~CachePolicy() {};virtual void put(Key key, Value value) = 0;virtual bool get(Key key,Value& value) = 0;virtual Value get(Key key) = 0;
};#endif

双向链表实现,包含节点结构体实现,用于存储键值对和前向后向节点指针,并且实现了双向链表基本功能,例如判断是否为空,增加、删除节点、获取链表头节点等方法

template<typename Key, typename Value> class LfuCache;template<typename Key, typename Value>
class FreqList
{
public:	struct Node//节点结构体,存储键值对和前向后向节点指针{int freq;Key key;Value value;std::shared_ptr<Node> pre;std::shared_ptr<Node> next;Node() :freq(1), pre(nullptr), next(nullptr) {}Node(Key key, Value value) :key(key), value(value), freq(1), pre(nullptr), next(nullptr) {}};using NodePtr = std::shared_ptr<Node>;int freq_;NodePtr head_;NodePtr tail_;public:	explicit FreqList(int n)://expicit禁止隐式构造freq_(n)//构建虚拟头节点和虚拟尾节点,然后头尾相连完成初始化{head_ = std::make_shared<Node>();tail_ = std::make_shared<Node>();head_->next = tail_;tail_->pre = head_;}//判空bool isEmpty() const{return head_->next == tail_;}//增加节点void addNode(NodePtr node){if(!node || !head_ || !tail_) return;node->pre = tail_->pre;node->next = tail_;tail_->pre->next = node;tail_->pre = node;}//删除节点void removeNode(NodePtr node){if(!node || !head_ || !tail_) return;if(!node->pre || !node->next) return;node->pre->next = node->next;node->next->pre = node->pre;node->pre = nullptr;node->next = nullptr;}//获取头节点NodePtr getFirstNode() const {return head_->next;}//类作为友元,方便LfuCache类直接访问FreqList类的私有成员friend class LfuCache<Key,Value>;
};

在 LFU 类中,实现了数据的增删改查功能。这里要着重讲一下计数操作,它是 LFU 算法的关键环节。想象一下,缓存就像是一个热闹的集市,里面的每个摊位(数据节点)都有自己的客流量(访问频次)。如果不对每个摊位的客流量计数加以限制,那些长期火爆的摊位(热数据),客流量计数就会像火箭一样不断飙升。这不仅会占用大量的 “记录空间”,就好比摊位的账本越写越厚,最后可能连存放账本的地方都没有了(占用空间甚至计数溢出)。而且,一些曾经很火但现在已经过时的摊位(过期的热点数据),因为之前积累的客流量太大,很难被挤出这个集市(难以被清除出缓存)。

为了解决这些问题,采用了一种巧妙的办法 —— 设置最大平均访问次数限制。它就像是给集市的热闹程度设定了一个上限。通过curTotalNum_这个 “总客流量计数器”,把集市里所有摊位的客流量都加起来,再除以摊位总数,就能得到平均每个摊位的客流量(平均访问次数curAverageNum_)。当这个平均客流量超过了设定的最大平均客流量maxAverageNum_后,就会启动handleOverMaxAverageNum这个 “摊位热度调整员”。它会把每个摊位的客流量都减去最大平均客流量的一半,这样一来,每个摊位的客流量上限就被控制住了,那些长期赖在集市里的过期热门摊位也终于有机会被清理出去了,集市(缓存)也就能够保持高效的运营状态啦。

template<typename Key, typename Value>
class LfuCache : public CachePolicy<Key,Value>
{
public:using Node = typename FreqList<Key,Value>::Node;using NodePtr = std::shared_ptr<Node>;using NodeMap = std::unordered_map<Key,NodePtr>;LfuCache(int capacity, int maxAverageNum = 10):capacity_(capacity),minFreq_(INT8_MAX),maxAverageNum_(maxAverageNum),curAverageNum_(0),curTotalNum_(0) {}~LfuCache() override = default;//存入缓存void put(Key key, Value value) override{if(capacity_ <= 0) return;std::lock_guard<std::mutex> lock(mutex_);auto it = nodeMap_.find(key);if(it != nodeMap_.end()){it->second->value = value;getInternal(it->second,value);return;}putInternal(key,value);}//读取缓存bool get(Key key, Value& value) override{std::lock_guard<std::mutex> lock(mutex_);auto it = nodeMap_.find(key);if(it != nodeMap_.end()){getInternal(it->second,value);return true;}return false;}//get重载Value get(Key key) override{Value value{};get(key,value);return value;}//清除所有缓存void purge(){nodeMap_.clear();freqToFreqList_.clear();}private:void putInternal(Key key, Value value);void getInternal(NodePtr key,Value& value);void kickOut();//根据最小访问计数minFreq_清除最不经常使用节点void removeFromFreqList(NodePtr node);//从双向链表中移除节点void addToFreqList(NodePtr node);//向双向链表加入节点void addFreqNum();//访问频次总数加1并计算当前平均访问次数,如果大于最大值调用节点处理函数void decreaseFreqNum(int num);//访问频次总数见num并计算当前平均访问次数void handleOverMaxAverageNum();//节点处理函数,对每个节点访问计数减去最大平均访问计数的一半,并调整freqToFreqList_这个哈希表void updateMinFreq();//遍历所有节点,找出最小访问计数private:int capacity_;//容量int minFreq_;//所有节点中访问计数的最小值int maxAverageNum_;//最大平均访问次数int curAverageNum_;//当前缓存的平均访问计数int curTotalNum_;//当前缓存中所有节点的访问计数总和std::mutex mutex_;NodeMap nodeMap_;//节点key与节点指针映射的哈希表std::unordered_map<int,FreqList<Key,Value>*> freqToFreqList_;//访问计数与双向链表映射的哈希表 
};

下面是私有成员方法的实现

template<typename Key, typename Value>
void LfuCache<Key,Value>::getInternal(NodePtr node, Value& value)
{value = node->value;removeFromFreqList(node);node->freq++;addToFreqList(node);
//判断并调整最小访问计数值if(node->freq - 1 == minFreq_ && freqToFreqList_[node->freq - 1]->isEmpty()){minFreq_++;}addFreqNum();
}template<typename Key, typename Value>
void LfuCache<Key,Value>::putInternal(Key key, Value value)
{
//如果缓存已满,清除最不经常使用节点if(nodeMap_.size() >= capacity_){kickOut();}NodePtr node = std::make_shared<Node>(key,value);nodeMap_[key] = node;addToFreqList(node);addFreqNum();minFreq_ = std::min(minFreq_,1);
}template<typename Key, typename Value>
void LfuCache<Key,Value>::kickOut()
{NodePtr node = freqToFreqList_[minFreq_]->getFirstNode();removeFromFreqList(node);nodeMap_.erase(node->key);decreaseFreqNum(node->freq);
}template<typename Key, typename Value>
void LfuCache<Key,Value>::addFreqNum()
{curTotalNum_++;if(nodeMap_.empty()){curAverageNum_ = 0;}else{curAverageNum_ = curTotalNum_ / nodeMap_.size();}if(curAverageNum_ > maxAverageNum_){handleOverMaxAverageNum();}
}template<typename Key, typename Value>
void LfuCache<Key,Value>::decreaseFreqNum(int num)
{curTotalNum_ -= num;if(nodeMap_.empty()){curAverageNum_ = 0;}else{curAverageNum_ = curTotalNum_ / nodeMap_.size();}
}template<typename Key, typename Value>
void LfuCache<Key,Value>::removeFromFreqList(NodePtr node)
{if(!node) return;auto freq = node->freq;freqToFreqList_[freq]->removeNode(node);
}template<typename Key, typename Value>
void LfuCache<Key,Value>::addToFreqList(NodePtr node)
{if(!node) return;auto freq = node->freq;if(freqToFreqList_.find(freq) == freqToFreqList_.end()){freqToFreqList_[freq] = new FreqList<Key,Value>(node->freq);}freqToFreqList_[freq]->addNode(node);
}
//这个操作逻辑有点像hashtable的重哈希扩容
template<typename Key, typename Value>
void LfuCache<Key,Value>::handleOverMaxAverageNum()
{if(nodeMap_.empty()) return;for(auto it = nodeMap_.begin(); it != nodeMap_.end(); it++){if(!it->second) continue;NodePtr node = it->second;//因为会改变访问计数值,其在freqList中的映射位置也会改变,所以先从双向链表内取出removeFromFreqList(node);//遍历所有节点,对节点访问计数减去最大平均计数值的一半,如果减后值小于0,则置为1node->freq -= maxAverageNum_ / 2;if(node->freq < 1) node->freq = 1;//插入双向链表addToFreqList(node);}updateMinFreq();
}template<typename Key, typename Value>
void LfuCache<Key,Value>::updateMinFreq()
{minFreq_ = INT8_MAX;for(const auto& pair : freqToFreqList_){if(pair.second && !pair.second->isEmpty()){minFreq_ = std::min(minFreq_,pair.first);}}if(minFreq_ == INT8_MAX)minFreq_ = 1;
}

通过以上代码也可以看出它和LRU有相同的锁粒度大问题,这样的话高并发下会消耗大量资源用于线程同步等待,这个问题怎么解决呢?其实可以把一个大的缓存池分成几个小的缓存池,就好比多个人都要从一个仓库取货,但仓库每次只能进一个人,如果多个人要取货那就必须得排队等待,哪怕每个人要取的货物不同,既然这样那不如把一个大仓库分成若干个小仓库,每个人取得货物可能分布在不同仓库内,哪怕仍然有好几个人要的货物碰巧在同一个仓库,那队伍也不会排的像原先那么长。这样就实现了分流。

template<typename Key, typename Value>
class HashLfuCache
{
public:HashLfuCache(size_t capacity, int sliceNum, int maxAverageNum = 10):	sliceNum_(sliceNum > 0 ? sliceNum : std::thread::hardware_concurrency()),capacity_(capacity){size_t sliceSize = std::ceil(capacity_ / static_cast<double>(sliceNum_));for(size_t i = 0; i < sliceNum_; i++){lfuSliceCaches_.emplace_back(new LfuCache<Key,Value>(sliceSize,maxAverageNum));}}void put(Key key, Value value){size_t sliceIndex = Hash(key) % sliceNum_;return lfuSliceCaches_[sliceIndex]->put(key,value);}bool get(Key key, Value& value){size_t sliceIndex = Hash(key) % sliceNum_;return lfuSliceCaches_[sliceIndex]->get(key,value);}Value get(Key key){Value value{};get(key,value);return value;}void purge(){for(auto& lfuSliceCache : lfuSliceCaches_){lfuSliceCache.purge();}}private:size_t Hash(Key key){std::hash<Key> hashFunc;return hashFunc(key);}private:size_t capacity_;int sliceNum_;std::vector<std::unique_ptr<LfuCache<Key, Value>>> lfuSliceCaches_;
};

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

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

相关文章

深度学习模型的部署实践与Web框架选择

引言 在深度学习项目的完整生命周期中&#xff0c;模型训练只是第一步&#xff0c;将训练好的模型部署到生产环境才能真正发挥其价值。本文将详细介绍模型部署的核心概念、常见部署方式以及三种主流Python Web框架的对比分析&#xff0c;帮助开发者选择最适合自己项目的技术方…

多功能气体检测报警系统,精准监测,守护安全

在化学品生产、石油化工、矿山、消防、环保、实验室等领域&#xff0c;有毒有害气体泄漏风险严重威胁工作人员和环境安全。化工企业生产中易产生大量可燃有毒气体&#xff0c;泄漏达一定浓度易引发爆炸、中毒等重大事故&#xff1b;矿井下瓦斯、一氧化碳等有害气体的浓度实时监…

lvgl多语言设置

搭建开发环境 安装node.js 安装node.js&#xff0c;点击进入官网地址 安装lv_i18n lv_i18n项目地址&#xff1a;Github&#xff1a;https://github.com/lvgl/lv_i18ngit运行命令安装lv_i18n&#xff1a;npm i lv_i18n -g。测试命令&#xff1a;lv_i18n -h 搭建过程报错 …

线程池技术

线程池基本概念 线程池就是在任务还没有到来前&#xff0c;预先创建一定数量的线程放入空闲列表。这些线程都是处于阻塞状态&#xff0c;不消耗CPU&#xff0c;但占用较小的内存空间。 当新任务到来时&#xff0c;缓冲池选择一个空线程&#xff0c;把任务传入此线程中运行&…

Go语言中的并发编程--详细讲解

文章目录 Go语言并发编程**简单介绍**goroutine channel 实现并发和并行for循环开启多个协程Channel管道goroutine 结合 channel 管道**goroutine 结合 channel打印素数**单向管道Select多路复用Goroutine Recover解决协程中出现的PanicGo中的并发安全和互斥锁 Go语言并发编程 …

C# NX二次开发:投影曲线和偏置曲线UFUN函数详解

大家好&#xff0c;今天要讲的是关于投影曲线和偏置曲线相关的函数。 &#xff08;1&#xff09;UF_CURVE_create_proj_curves1&#xff1a;这个函数的定义为创建投影曲线。 Defined in: uf_curve.h Overview Creates projection curves. Objects to project may be poi…

用R语言+随机森林玩转遥感空间预测-基于R语言机器学习遥感数据处理与模型空间预测技术及实际项目案例分析

遥感数据具有高维度、非线性及空间异质性等特点&#xff0c;传统分析方法往往难以充分挖掘其信息价值。机器学习技术的引入为遥感数据处理与模型预测提供了新的解决方案&#xff0c;其中随机森林&#xff08;Random Forest&#xff09;以其优异的性能和灵活性成为研究者的首选工…

unity 导入图片后,可选择精灵表自动切片,并可以导出为png

脚本源代码&#xff1a; #if UNITY_EDITOR using UnityEditor; using UnityEngine; using System.IO; using UnityEditorInternal; using System.Collections.Generic; using System;public class TextureImporterWindow : EditorWindow {private string folderPath "D:…

使用 Azure DevSecOps 和 AIOps 构建可扩展且安全的多区域金融科技 SaaS 平台

引言 金融科技行业有一个显著特点&#xff1a;客户期望能够随时随地即时访问其财务数据&#xff0c;并且对宕机零容忍。即使是短暂的中断也会损害用户的信心和忠诚度。与此同时&#xff0c;对数据泄露的担忧已将安全提升到整个行业的首要地位。 在本文中&#xff0c;我们将探…

基于Django框架开发的B2C天天生鲜电商平台

天天生鲜 介绍 天天生鲜是一个基于Django框架开发的B2C(Business-to-Customer)电商平台&#xff0c;专注于生鲜食品的在线销售。该项目采用了主流的Python Web开发框架Django&#xff0c;结合MySQL数据库、Redis缓存等技术&#xff0c;实现了一个功能完整、界面友好的电商网站…

ASP.NET MVC4 技术单选及多选题目汇编

一、单选题&#xff08;共50题&#xff0c;每题2分&#xff09; 1、ASP.NET MVC4 的核心架构模式是什么&#xff1f; A. MVP B. MVVM C. MVC D.三层架构 答案&#xff1a;C 2、在 MVC4 中&#xff0c;默认的路由配置文件名是&#xff1f; A. Global.asax B. RouteConfig.cs C.…

26届秋招收割offer指南

26届暑期实习已经陆续启动&#xff0c;这也意味着对于26届的同学们来说&#xff0c;“找工作”已经提上了日程。为了帮助大家更好地准备暑期实习和秋招&#xff0c;本期主要从时间线、学习路线、核心知识点及投递几方面给大家介绍&#xff0c;希望能为大家提供一些实用的建议和…

数据中心机电建设

电气系统 供配电系统 设计要求&#xff1a;数据中心通常需要双路市电供电&#xff0c;以提高供电的可靠性。同时&#xff0c;配备柴油发电机组作为备用电源&#xff0c;确保在市电停电时能及时为关键设备供电。根据数据中心的规模和设备功耗&#xff0c;精确计算电力负荷&…

每日一题洛谷P1025 [NOIP 2001 提高组] 数的划分c++

P1025 [NOIP 2001 提高组] 数的划分 - 洛谷 (luogu.com.cn) #include<iostream> using namespace std; int n, k; int res 0; void dfs(int num,int step,int sum) {//判断if (sum n) {if (step k) {res;return;}}if (sum > n || step k)return;//搜索for (int i …

大模型推理--从零搭建大模型推理服务器:硬件选购、Ubuntu双系统安装与环境配置

自从大模型火了之后就一直想自己组装一台机器去深入研究一下大模型&#xff0c;奈何囊中羞涩&#xff0c;迟迟也没有行动。在下了很大的勇气之后&#xff0c;终于花了接近4万块钱组装了一台台式机&#xff0c;下面给大家详细介绍一下我的装机过程。 1.硬件配置 研究了一周&am…

第35周Zookkeeper+Dubbo Dubbo

Dubbo 详解 一、Dubbo 是什么 官网与定义 Dubbo 是一款高性能、轻量级的开源服务框架&#xff0c;其官网为 double.apache.org&#xff0c;提供中文版本&#xff08;网址含 “zh”&#xff09;。 核心能力 Dubbo 具备六大核心能力&#xff1a; 面向接口代理的高性能 RPC …

NX二次开发——BlockUI 弹出另一个BlockUI对话框

最近在研究&#xff0c;装配体下自动导出BOM表格中需要用到BlockUI 弹出另一个BlockUI对话框。通过对网上资料进行整理总结&#xff0c;具体如下&#xff1a; 1、明确主对话框、子对话框1和子对话框2 使用BlockUI创建.cpp和.hpp文件&#xff0c;dlx文件内容如下所示 主对话框…

PostgreSQL 系统管理函数详解

PostgreSQL 系统管理函数详解 PostgreSQL 提供了一系列强大的系统管理函数&#xff0c;用于数据库维护、监控和配置。这些函数可分为多个类别&#xff0c;以下是主要功能的详细说明&#xff1a; 一、数据库配置函数 1. 参数管理函数 -- 查看所有配置参数 SELECT name, sett…

【2025软考高级架构师】——计算机网络(9)

摘要 全文主要围绕计算机网络相关知识展开&#xff0c;包括域名服务器查询方式、网络规划与设计的关键技术、双协议栈与隧道技术、层次化网络设计、网络冗余设计以及高可靠和高可用性等方面&#xff0c;旨在为软考高级架构师的备考提供知识参考。 1. 通信网络架构图 2. 通信架…

yolov8n-obb训练rknn模型

必备&#xff1a; 准备一台ubuntu22的服务器或者虚拟机&#xff08;x86_64&#xff09; 1、数据集标注&#xff1a; 1&#xff09;推荐使用X-AnyLabeling标注工具 2&#xff09;标注选【旋转框】 3&#xff09;可选AI标注&#xff0c;再手动补充&#xff0c;提高标注速度 …