2021SC@SDUSC
LRUCache
Cache在计算机组成原理中讲解过,它的速度介于寄存器和内存之间,能解决CPU运算速率与内存读写速率不匹配的矛盾。
引入Cache的理论基础是程序局部性原理:最近被CPU访问的数据,短期内CPU还要访问(时间局部性);被CPU访问的数据附近的数据,CPU短期内还要访问(空间局部性)。
缓存的容量较小,如果在某个时间点新的块传入,而cache满了,就会需要替换掉cache块。workflow中缓存替换策略为LRU,近期最少使用策略。学习操作系统时,讲过LRU的页面置换算法。这里十分类似,维护一个缓存对象列表,其中对象列表的排列方式是按照访问顺序实现的,即一直没访问的对象,将放在链尾,即将被淘汰。而最近访问的对象将放在链头,最后被淘汰。
// put copy
// Need call release when handle no longer needed
const Handle *put(const KEY& key, VALUE value)
{
Handle *e = new Handle(key, value);
e->ref = 1;
std::lock_guard<std::mutex> lock(mutex_);
size_++;
e->in_cache = true;
e->ref++;
list_append(&in_use_, e);
MapIterator it = cache_map_.find(key);
if (it != cache_map_.end())
{
erase_node(it->second);
it->second = e;
}
else
cache_map_[key] = e;
if (max_size_ > 0)
{
while (size_ > max_size_ && not_use_.next != ¬_use_)
{
Handle *old = not_use_.next;
assert(old->ref == 1);
cache_map_.erase(old->key);
erase_node(old);
}
}
return e;
}
void list_append(Handle *list, Handle *node)
{
node->next = list;
node->prev = list->prev;
node->prev->next = node;
node->next->prev = node;
}
需要维护 in_use_、not_use_两个双向链表,记录每个节点的前驱和后继保证删除和插入的时间复杂度都是O(1)。 信号量机制用来保护共享资源,使得cache块在同一个时刻不会被多个进程操作。
const Handle *put(const KEY& key, VALUE value)函数中,e为最近访问的节点,需要将它的in_cache属性设置为true。然后调用list_append(&in_use_, e)将e加入到in_use_的头部,还要根据cache块的键值查找,每个块只能保存在特定的cache地址,如果已有内容,原先的块会被替换出去(直接替换),所以workflow中cache的映射方式应该是直接映射。最后如果超出了cache的容量,还需要删除not_use_中的结点。
// Remove all cache that are not actively in use.
void prune()
{
std::lock_guard<std::mutex> lock(mutex_);
while (not_use_.next != ¬_use_)
{
Handle *e = not_use_.next;
assert(e->ref == 1);
cache_map_.erase(e->key);
erase_node(e);
}
}
prune()函数在cache块需要替换的时候,去除not_use_链表中的所有节点(最近不常使用的节点),同样也有信号量的使用来保证不会被多个进程同时操作。
const Handle *get(const KEY& key)
{
std::lock_guard<std::mutex> lock(mutex_);
MapConstIterator it = cache_map_.find(key);
if (it != cache_map_.end())
{
ref(it->second);
return it->second;
}
return NULL;
}
*get(const KEY& key) 访问键值为key的cache块,通过映射关系查找,如果命中返回相应内容,如果不在cache中就返回NULL。
DNSCache
DNSCache顾名思义,用来保存DNS信息的缓存。由于访问缓存的速度快于访问内存的速度,在框架中引入缓存机制能够加快域名解析的速度。
struct DNSCacheValue
{
struct addrinfo *addrinfo;
int64_t confident_time;
int64_t expire_time;
};
addrinfo结构体在上篇博客中有介绍,主要在网络编程解析hostname时使用,它能够支持一个域名对应多个IP地址的情况。上篇博客中也提到一个重要的变量TTL(Time To Live) 表示DNS记录在DNS Cache上的时间。
int64_t并不是一个新的数据类型,可以理解成typedef long long,这样表示是为了跨平台方便维护,int64_t在任何平台上都为64位宽,而long在不同平台上可能具有不同的长度。expire_time我理解成Cache上信息的保存时间。confident_time我暂时理解为Cache中内容可信的时间(没有被修改),在DNSCache.h文件中没有对这两者详细描述,后续的分析中看见了来填坑。
参考资料:
struct addrinfo原理及操作方法总结 - 简书 (jianshu.com)
int,int32_t,int64_t - brave-sailor - 博客园 (cnblogs.com)
CPU Cache 机制以及 Cache miss - JokerJason - 博客园 (cnblogs.com)
(36条消息) C中int8_t、int16_t、int32_t、int64_t、uint8_t、size_t、ssize_t区别_Yummy-CSDN博客_int32_t缓存(cache)介绍 - 知乎 (zhihu.com)
(37条消息) Cache —— Cache和主存的映射方式_starter_____的博客-CSDN博客_cache和主存的映射方式