LRU(最近最久未使用)算法的主要不足在于它只考虑时间局部性,当遇到突发性的冷数据访问时,可能会将热点数据挤出缓存,造成缓存污染。例如,如果缓存大小为4,当前缓存中有热点数据A、B、C、D,突然有大量冷数据E、F、G、H访问,这些冷数据会依次替换掉热点数据,导致缓存命中率急剧下降。而LFU(最近最少使用)算法虽然考虑了访问频率,但它只关注历史访问次数,无法及时响应访问模式的变化,比如一个曾经频繁访问的数据如果最近不再访问,它仍然会占用缓存空间,而且新加入的数据由于访问次数少,容易被频繁访问的数据挤出缓存。ARC(自适应替换缓存)算法通过结合LRU和LFU的优点,并引入幽灵缓存机制来解决这些问题。ARC维护四个列表:T1(最近一次访问的数据)、B1(T1的幽灵缓存)、T2(多次访问的数据)和B2(T2的幽灵缓存)。
LRU
对应链表的尾部插入,这里尾部表示新数据。transformTime_
(自定义的变量,我这里设置为3,代表访问次数超过3次),若大于则将该数据插入LFU
对应链表中(一个块至少需要读取transformTime_
次,并且要是最近请求的数据,才会被存储到LFU
中)。于是,该数据块不仅仅只保存在LRU的缓存目录中,也将保存到LFU中。如果命中且LFU链表中存在,则将数据重新放入LFU链表中对应位置(访问频次计数+1),这样,那些真正被频繁访问的页面将一直呆在缓存中,不会被冷数据的加入而误淘汰,不经常访问的数据会向链表头部移动,最终被淘汰出去。LRU
缓存满了,则从LRU
链表中淘汰表头部的数据,将淘汰数据的key
放入LRU
对应的ghost list
。然后在LRU
的链表尾部添加新数据。如果ghost list
的元素满了,按照先进先出的方式淘汰ghost list
中的元素头部元素,然后再从尾部插入元素。ghost list
中,则表示幽灵命中,缓存系统就可以知道,这是一个刚刚淘汰的页面,而不是第一次读取或者说很久之前读取的一个页面。于是根据这个信息来调整内部的partition
分割指针以适应当下的访问模式。上述迹象说明当前的LRU
缓存太小了,于是将partition
分割指针右移一位(也就是LRU
缓存空间+1,LFU
缓存空间-1),并将命中的key
数据从ghost
的中移除,将新数据从LRU链表尾部插入。partition
分割指针左移一位,并将命中的key
数据从ghost
的中移除,将新数据从LRU链表尾部插入。哈希表提供 O(1) 的查找性能; 双向链表维护访问顺序; 智能指针(shared_ptr)自动管理内存,避免内存泄漏; 链表节点包含前后指针,方便快速移动和删除。
map 按频率排序,方便找到最小频率; list 存储同频率节点,支持快速插入删除; 哈希表提供 O(1) 的节点查找; 分离频率管理和节点存储,提高效率。
保护共享资源访问; RAII 方式自动管理锁; 避免死锁和竞态条件; 保证数据一致性
shared_ptr 实现引用计数; 自动管理节点生命周期; 避免内存泄漏; 支持多线程安全
#pragma once
#include "../KICachePolicy.h"
#include "KArcLruPart.h"
#include "KArcLfuPart.h"
#include
namespace KamaCache
{
template
class KArcCache : public KICachePolicy
{
public:
explicit KArcCache(size_t capacity = 10, size_t transformThreshold = 2)
: capacity_(capacity)
, transformThreshold_(transformThreshold)
, lruPart_(std::make_unique>(capacity, transformThreshold))
, lfuPart_(std::make_unique>(capacity, transformThreshold))
{}
~KArcCache() override = default;
void put(Key key, Value value) override
{
bool inGhost = checkGhostCaches(key);
if (!inGhost)
{
if (lruPart_->put(key, value))
{
lfuPart_->put(key, value);
}
} else
{
lruPart_->put(key, value);
}
}
bool get(Key key, Value& value) override
{
checkGhostCaches(key);
bool shouldTransform = false;
//检查LRU的幽灵缓存
if (lruPart_->get(key, value, shouldTransform))
{
if (shouldTransform)
{
lfuPart_->put(key, value);
}
return true;
}
//检查LFU的幽灵缓存
return lfuPart_->get(key, value);
}
Value get(Key key) override
{
Value value{};
get(key, value);
return value;
}
private:
bool checkGhostCaches(Key key)
{
bool inGhost = false;
if (lruPart_->checkGhost(key))
{
if (lfuPart_->decreaseCapacity())
{
lruPart_->increaseCapacity();
}
inGhost = true;
}
else if (lfuPart_->checkGhost(key))
{
if (lruPart_->decreaseCapacity())
{
lfuPart_->increaseCapacity();
}
inGhost = true;
}
return inGhost;
}
private:
size_t capacity_;
size_t transformThreshold_;
std::unique_ptr> lruPart_;
std::unique_ptr> lfuPart_;
};
} // namespace KamaCache
#pragma once
#include
namespace KamaCache
{
template
class ArcNode
{
private:
Key key_;
Value value_;
size_t accessCount_;
std::shared_ptr prev_;
std::shared_ptr next_;
public:
ArcNode() : accessCount_(1), prev_(nullptr), next_(nullptr) {}
ArcNode(Key key, Value value)
: key_(key)
, value_(value)
, accessCount_(1)
, prev_(nullptr)
, next_(nullptr)
{}
// Getters
Key getKey() const { return key_; }
Value getValue() const { return value_; }
size_t getAccessCount() const { return accessCount_; }
// Setters
void setValue(const Value& value) { value_ = value; }
void incrementAccessCount() { ++accessCount_; }
template friend class ArcLruPart;
template friend class ArcLfuPart;
};
} // namespace KamaCache
lru
#pragma once
#include "KArcCacheNode.h"
#include
#include
namespace KamaCache
{
template
class ArcLruPart
{
public:
using NodeType = ArcNode;
using NodePtr = std::shared_ptr;
using NodeMap = std::unordered_map;
explicit ArcLruPart(size_t capacity, size_t transformThreshold)
: capacity_(capacity)
, ghostCapacity_(capacity)
, transformThreshold_(transformThreshold)
{
initializeLists();
}
bool put(Key key, Value value)
{
if (capacity_ == 0) return false;
std::lock_guard lock(mutex_);
auto it = mainCache_.find(key);
if (it != mainCache_.end())
{
return updateExistingNode(it->second, value);
}
return addNewNode(key, value);
}
bool get(Key key, Value& value, bool& shouldTransform)
{
std::lock_guard lock(mutex_); //加锁保护
auto it = mainCache_.find(key); //在哈希表中查找
if (it != mainCache_.end())
{
shouldTransform = updateNodeAccess(it->second); //更新节点访问次数
value = it->second->getValue(); //获取节点值
return true;
}
return false;
}
bool checkGhost(Key key)
{
auto it = ghostCache_.find(key);
if (it != ghostCache_.end()) {
removeFromGhost(it->second);
ghostCache_.erase(it);
return true;
}
return false;
}
void increaseCapacity() { ++capacity_; }
bool decreaseCapacity()
{
if (capacity_ <= 0) return false;
if (mainCache_.size() == capacity_) {
evictLeastRecent();
}
--capacity_;
return true;
}
private:
void initializeLists()
{
mainHead_ = std::make_shared();
mainTail_ = std::make_shared();
mainHead_->next_ = mainTail_;
mainTail_->prev_ = mainHead_;
ghostHead_ = std::make_shared();
ghostTail_ = std::make_shared();
ghostHead_->next_ = ghostTail_;
ghostTail_->prev_ = ghostHead_;
}
bool updateExistingNode(NodePtr node, const Value& value)
{
node->setValue(value);
moveToFront(node);
return true;
}
bool addNewNode(const Key& key, const Value& value)
{
if (mainCache_.size() >= capacity_)
{
evictLeastRecent(); // 驱逐最近最少访问
}
NodePtr newNode = std::make_shared(key, value);
mainCache_[key] = newNode;
addToFront(newNode);
return true;
}
bool updateNodeAccess(NodePtr node)
{
moveToFront(node); //移动到链表前端
node->incrementAccessCount(); //增加访问次数
return node->getAccessCount() >= transformThreshold_; //判断是否需要调整阈值
}
void moveToFront(NodePtr node)
{
// 先从当前位置移除
node->prev_->next_ = node->next_;
node->next_->prev_ = node->prev_;
// 添加到头部
addToFront(node);
}
void addToFront(NodePtr node)
{
node->next_ = mainHead_->next_;
node->prev_ = mainHead_;
mainHead_->next_->prev_ = node;
mainHead_->next_ = node;
}
void evictLeastRecent()
{
NodePtr leastRecent = mainTail_->prev_;
if (leastRecent == mainHead_)
return;
// 从主链表中移除
removeFromMain(leastRecent);
// 添加到幽灵缓存
if (ghostCache_.size() >= ghostCapacity_)
{
removeOldestGhost();
}
addToGhost(leastRecent);
// 从主缓存映射中移除
mainCache_.erase(leastRecent->getKey());
}
void removeFromMain(NodePtr node)
{
node->prev_->next_ = node->next_;
node->next_->prev_ = node->prev_;
}
void removeFromGhost(NodePtr node)
{
node->prev_->next_ = node->next_;
node->next_->prev_ = node->prev_;
}
void addToGhost(NodePtr node)
{
// 重置节点的访问计数
node->accessCount_ = 1;
// 添加到幽灵缓存的头部
node->next_ = ghostHead_->next_;
node->prev_ = ghostHead_;
ghostHead_->next_->prev_ = node;
ghostHead_->next_ = node;
// 添加到幽灵缓存映射
ghostCache_[node->getKey()] = node;
}
void removeOldestGhost()
{
NodePtr oldestGhost = ghostTail_->prev_;
if (oldestGhost == ghostHead_)
return;
removeFromGhost(oldestGhost);
ghostCache_.erase(oldestGhost->getKey());
}
private:
size_t capacity_;
size_t ghostCapacity_;
size_t transformThreshold_; // 转换门槛值
std::mutex mutex_;
NodeMap mainCache_; // key -> ArcNode
NodeMap ghostCache_;
// 主链表
NodePtr mainHead_;
NodePtr mainTail_;
// 淘汰链表
NodePtr ghostHead_;
NodePtr ghostTail_;
};
} // namespace KamaCache
lfu:
#pragma once
#include "KArcCacheNode.h"
#include
#include
#pragma once
#include "../KICachePolicy.h"
#include "KArcLruPart.h"
#include "KArcLfuPart.h"
#include
namespace KamaCache
{
template
class KArcCache : public KICachePolicy
{
public:
explicit KArcCache(size_t capacity = 10, size_t transformThreshold = 2)
: capacity_(capacity)
, transformThreshold_(transformThreshold)
, lruPart_(std::make_unique>(capacity, transformThreshold))
, lfuPart_(std::make_unique>(capacity, transformThreshold))
{}
~KArcCache() override = default;
void put(Key key, Value value) override
{
bool inGhost = checkGhostCaches(key);
if (!inGhost)
{
if (lruPart_->put(key, value))
{
lfuPart_->put(key, value);
}
} else
{
lruPart_->put(key, value);
}
}
bool get(Key key, Value& value) override
{
checkGhostCaches(key);
bool shouldTransform = false;
//检查LRU的幽灵缓存
if (lruPart_->get(key, value, shouldTransform))
{
if (shouldTransform)
{
lfuPart_->put(key, value);
}
return true;
}
//检查LFU的幽灵缓存
return lfuPart_->get(key, value);
}
Value get(Key key) override
{
Value value{};
get(key, value);
return value;
}
private:
bool checkGhostCaches(Key key)
{
bool inGhost = false;
if (lruPart_->checkGhost(key))
{
if (lfuPart_->decreaseCapacity())
{
lruPart_->increaseCapacity();
}
inGhost = true;
}
else if (lfuPart_->checkGhost(key))
{
if (lruPart_->decreaseCapacity())
{
lfuPart_->increaseCapacity();
}
inGhost = true;
}
return inGhost;
}
private:
size_t capacity_;
size_t transformThreshold_;
std::unique_ptr> lruPart_;
std::unique_ptr> lfuPart_;
};
} // namespace KamaCache
LRU 部分通过 ArcLruPart 类实现,维护了一个主缓存(mainCache_)和一个幽灵缓存(ghostCache_)。主缓存使用哈希表(unordered_map)和双向链表相结合的数据结构,哈希表存储 key 到节点的映射实现 O(1) 查找,双向链表维护数据的访问顺序。当调用 get 操作时,首先通过 std::lock_guard 加锁保证线程安全,然后在 mainCache_ 中查找目标 key。如果找到,会触发一系列更新操作:调用 updateNodeAccess 函数增加节点的访问计数(accessCount_),同时通过 moveToFront 函数将节点移动到链表前端(表示最近访问)。moveToFront 操作分两步:先通过调整前后节点的指针将当前节点从原位置移除,然后通过 addToFront 函数将节点插入到链表头部(mainHead_ 之后)。如果节点的访问次数达到转换阈值(transformThreshold_),则标记 shouldTransform 为 true,表示该节点应该转移到 LFU 部分。当缓存满时,通过 evictLeastRecent 函数移除链表尾部的节点(最久未使用),被移除的节点会被放入幽灵缓存中,记录历史访问信息。整个过程通过互斥锁(mutex_)保证线程安全,通过智能指针(shared_ptr)管理节点内存。
KArcCache::get(key, value)
-> checkGhostCaches(key)
-> lruPart_->checkGhost(key)
-> ghostCache_.find(key)
-> removeFromGhost(node)
-> ghostCache_.erase(key)
-> lfuPart_->decreaseCapacity()
-> lruPart_->increaseCapacity()
-> lruPart_->get(key, value, shouldTransform)
-> std::lock_guard lock
-> mainCache_.find(key)
-> updateNodeAccess(node)
-> moveToFront(node)
-> node->prev_->next_ = node->next_
-> node->next_->prev_ = node->prev_
-> addToFront(node)
-> node->next_ = mainHead_->next_
-> node->prev_ = mainHead_
-> mainHead_->next_->prev_ = node
-> mainHead_->next_ = node
-> node->incrementAccessCount()
-> return node->getAccessCount() >= transformThreshold_
-> node->getValue()
相关数据结构:
// 节点结构
template
class ArcNode {
Key key_; // 键
Value value_; // 值
size_t accessCount_; // 访问计数
std::shared_ptr prev_; // 前向指针
std::shared_ptr next_; // 后向指针
};
// LRU部分主要数据结构
class ArcLruPart {
size_t capacity_; // 缓存容量
size_t ghostCapacity_; // 幽灵缓存容量
size_t transformThreshold_; // 转换阈值
std::mutex mutex_; // 互斥锁
NodeMap mainCache_; // 主缓存哈希表
NodeMap ghostCache_; // 幽灵缓存哈希表
NodePtr mainHead_; // 主链表头节点
NodePtr mainTail_; // 主链表尾节点
NodePtr ghostHead_; // 幽灵链表头节点
NodePtr ghostTail_; // 幽灵链表尾节点
};
LFU 部分通过 ArcLfuPart 类实现,同样维护主缓存(mainCache_)和幽灵缓存(ghostCache_),但增加了频率管理机制。除了哈希表存储 key 到节点的映射,还使用 freqMap_ (map
KArcCache::get(key, value)
-> checkGhostCaches(key)
-> lfuPart_->checkGhost(key)
-> ghostCache_.find(key)
-> removeFromGhost(node)
-> ghostCache_.erase(key)
-> lruPart_->decreaseCapacity()
-> lfuPart_->increaseCapacity()
-> lfuPart_->get(key, value)
-> std::lock_guard lock
-> mainCache_.find(key)
-> updateNodeFrequency(node)
-> oldFreq = node->getAccessCount()
-> node->incrementAccessCount()
-> newFreq = node->getAccessCount()
-> freqMap_[oldFreq].remove(node)
-> if (oldList.empty())
-> freqMap_.erase(oldFreq)
-> update minFreq_
-> freqMap_[newFreq].push_back(node)
-> node->getValue()
相关数据结构:
// 节点结构 (与LRU共用ArcNode)
template
class ArcNode {
Key key_; // 键
Value value_; // 值
size_t accessCount_; // 访问计数
std::shared_ptr prev_; // 前向指针
std::shared_ptr next_; // 后向指针
};
// LFU部分主要数据结构
class ArcLfuPart {
size_t capacity_; // 缓存容量
size_t ghostCapacity_; // 幽灵缓存容量
size_t transformThreshold_; // 转换阈值
size_t minFreq_; // 最小频率
std::mutex mutex_; // 互斥锁
NodeMap mainCache_; // 主缓存哈希表
NodeMap ghostCache_; // 幽灵缓存哈希表
FreqMap freqMap_; // 频率映射表
NodePtr ghostHead_; // 幽灵链表头节点
NodePtr ghostTail_; // 幽灵链表尾节点
};
// 频率映射表类型定义
using FreqMap = std::map>;
共享的部分:
// 类型定义
using NodeType = ArcNode;
using NodePtr = std::shared_ptr;
using NodeMap = std::unordered_map;
// ArcCache主类数据结构
class KArcCache {
size_t capacity_; // 总缓存容量
size_t transformThreshold_; // 转换阈值
std::unique_ptr> lruPart_; // LRU部分
std::unique_ptr> lfuPart_; // LFU部分
};
LRU 部分的 put 操作通过 ArcLruPart 类实现,当调用 put 操作时,首先通过 std::lock_guard 加锁保证线程安全,然后在 mainCache_ 中查找目标 key。如果找到已存在的节点,调用 updateExistingNode 函数更新节点:先通过 setValue 更新节点的值,然后通过 moveToFront 函数将节点移动到链表前端,表示最近访问。如果节点不存在,则调用 addNewNode 函数添加新节点:首先检查缓存是否已满,如果已满则调用 evictLeastRecent 函数移除最久未使用的节点(链表尾部节点),被移除的节点会被放入幽灵缓存中。然后创建新的 ArcNode 节点,设置其 key、value 和初始访问计数为1,将节点插入到 mainCache_ 哈希表中,最后通过 addToFront 函数将节点添加到链表头部。整个过程通过互斥锁保证线程安全,通过智能指针管理节点内存。当节点被驱逐时,会先调用 removeFromMain 函数从主链表中移除,然后通过 addToGhost 函数将其添加到幽灵缓存中,同时重置其访问计数为1,这样可以记录历史访问信息,帮助系统做出更好的缓存替换决策。
KArcCache::put(key, value)
-> checkGhostCaches(key)
-> lruPart_->checkGhost(key)
-> ghostCache_.find(key)
-> removeFromGhost(node)
-> ghostCache_.erase(key)
-> lfuPart_->decreaseCapacity()
-> lruPart_->increaseCapacity()
-> lruPart_->put(key, value)
-> std::lock_guard lock
-> mainCache_.find(key)
-> updateExistingNode(node, value)
-> node->setValue(value)
-> moveToFront(node)
-> node->prev_->next_ = node->next_
-> node->next_->prev_ = node->prev_
-> addToFront(node)
-> node->next_ = mainHead_->next_
-> node->prev_ = mainHead_
-> mainHead_->next_->prev_ = node
-> mainHead_->next_ = node
-> addNewNode(key, value)
-> if (mainCache_.size() >= capacity_)
-> evictLeastRecent()
-> NodePtr leastRecent = mainTail_->prev_
-> removeFromMain(leastRecent)
-> addToGhost(leastRecent)
-> mainCache_.erase(leastRecent->getKey())
-> NodePtr newNode = std::make_shared(key, value)
-> mainCache_[key] = newNode
-> addToFront(newNode)
LFU 部分的 put 操作通过 ArcLfuPart 类实现,同样先加锁保护,然后在 mainCache_ 中查找 key。如果找到已存在的节点,调用 updateExistingNode 函数更新节点:更新节点值并通过 updateNodeFrequency 函数更新其访问频率。如果节点不存在,则调用 addNewNode 函数添加新节点:首先检查缓存是否已满,如果已满则调用 evictLeastFrequent 函数移除访问频率最低的节点。evictLeastFrequent 函数会从 freqMap_ 中找到最小频率(minFreq_)对应的列表,移除该列表中的第一个节点,如果该频率列表为空则删除该频率项并更新最小频率。被移除的节点通过 addToGhost 函数添加到幽灵缓存中。然后创建新的 ArcNode 节点,设置其 key、value 和初始访问计数为1,将节点插入到 mainCache_ 哈希表中,同时将其添加到 freqMap_ 中频率为1的列表中,并更新最小频率为1。整个过程通过互斥锁保证线程安全,通过智能指针管理内存,并通过频率管理机制实现了基于使用频率的缓存替换策略。当节点被驱逐时,会先调用 removeFromGhost 函数从幽灵链表中移除,然后通过 addToGhost 函数将其添加到幽灵缓存的头部,这样可以保持历史访问信息,帮助系统做出更好的缓存替换决策。
KArcCache::put(key, value)
-> checkGhostCaches(key)
-> lfuPart_->checkGhost(key)
-> ghostCache_.find(key)
-> removeFromGhost(node)
-> ghostCache_.erase(key)
-> lruPart_->decreaseCapacity()
-> lfuPart_->increaseCapacity()
-> lfuPart_->put(key, value)
-> std::lock_guard lock
-> mainCache_.find(key)
-> updateExistingNode(node, value)
-> node->setValue(value)
-> updateNodeFrequency(node)
-> oldFreq = node->getAccessCount()
-> node->incrementAccessCount()
-> newFreq = node->getAccessCount()
-> freqMap_[oldFreq].remove(node)
-> if (oldList.empty())
-> freqMap_.erase(oldFreq)
-> update minFreq_
-> freqMap_[newFreq].push_back(node)
-> addNewNode(key, value)
-> if (mainCache_.size() >= capacity_)
-> evictLeastFrequent()
-> auto& minFreqList = freqMap_[minFreq_]
-> NodePtr leastNode = minFreqList.front()
-> minFreqList.pop_front()
-> if (minFreqList.empty())
-> freqMap_.erase(minFreq_)
-> update minFreq_
-> addToGhost(leastNode)
-> mainCache_.erase(leastNode->getKey())
-> NodePtr newNode = std::make_shared(key, value)
-> mainCache_[key] = newNode
-> freqMap_[1].push_back(newNode)
-> minFreq_ = 1
数据结构部分:
// 节点结构
template
class ArcNode {
Key key_; // 键
Value value_; // 值
size_t accessCount_; // 访问计数
std::shared_ptr prev_; // 前向指针
std::shared_ptr next_; // 后向指针
};
// LRU部分主要数据结构
class ArcLruPart {
size_t capacity_; // 缓存容量
size_t ghostCapacity_; // 幽灵缓存容量
size_t transformThreshold_; // 转换阈值
std::mutex mutex_; // 互斥锁
NodeMap mainCache_; // 主缓存哈希表
NodeMap ghostCache_; // 幽灵缓存哈希表
NodePtr mainHead_; // 主链表头节点
NodePtr mainTail_; // 主链表尾节点
NodePtr ghostHead_; // 幽灵链表头节点
NodePtr ghostTail_; // 幽灵链表尾节点
};
// LFU部分主要数据结构
class ArcLfuPart {
size_t capacity_; // 缓存容量
size_t ghostCapacity_; // 幽灵缓存容量
size_t transformThreshold_; // 转换阈值
size_t minFreq_; // 最小频率
std::mutex mutex_; // 互斥锁
NodeMap mainCache_; // 主缓存哈希表
NodeMap ghostCache_; // 幽灵缓存哈希表
FreqMap freqMap_; // 频率映射表
NodePtr ghostHead_; // 幽灵链表头节点
NodePtr ghostTail_; // 幽灵链表尾节点
};
// 类型定义
using NodeType = ArcNode;
using NodePtr = std::shared_ptr;
using NodeMap = std::unordered_map;
using FreqMap = std::map>;
// ArcCache主类数据结构
class KArcCache {
size_t capacity_; // 总缓存容量
size_t transformThreshold_; // 转换阈值
std::unique_ptr> lruPart_; // LRU部分
std::unique_ptr> lfuPart_; // LFU部分
};