C++ LRU 缓存 - 需要有关如何提高速度的建议
Posted
技术标签:
【中文标题】C++ LRU 缓存 - 需要有关如何提高速度的建议【英文标题】:C++ LRU cache - need suggestions on how to improve speed 【发布时间】:2020-04-27 04:48:21 【问题描述】:任务是实现一个 O(1) 最近最少使用的缓存
这是关于leetcode的问题https://leetcode.com/problems/lru-cache/
这是我的解决方案,虽然它是 O(1),但它不是最快的实现您能否提供一些反馈,或者关于如何优化它的想法?谢谢!
#include<unordered_map>
#include<list>
class LRUCache
// umap<key,<value,listiterator>>
// store the key,value, position in list(iterator) where push_back occurred
private:
unordered_map<int,pair<int,list<int>::iterator>> umap;
list<int> klist;
int cap = -1;
public:
LRUCache(int capacity):cap(capacity)
int get(int key)
// if the key exists in the unordered map
if(umap.count(key))
// remove it from the old position
klist.erase(umap[key].second);
klist.push_back(key);
list<int>::iterator key_loc = klist.end();
umap[key].second = --key_loc;
return umap[key].first;
return -1;
void put(int key, int value)
// if key already exists delete it from the the umap and klist
if(umap.count(key))
klist.erase(umap[key].second);
umap.erase(key);
// if the unordered map is at max capacity
if(umap.size() == cap)
umap.erase(klist.front());
klist.pop_front();
// finally update klist and umap
klist.push_back(key);
list<int>::iterator key_loc = klist.end();
umap[key].first = value;
umap[key].second = --key_loc;
return;
;
/**
* Your LRUCache object will be instantiated and called as such:
* LRUCache* obj = new LRUCache(capacity);
* int param_1 = obj->get(key);
* obj->put(key,value);
*/
【问题讨论】:
从技术上讲,地图大小不应超过容量,否则这是一个错误的设计 从实际操作系统的角度来看是有意义的。 【参考方案1】:以下一些优化可能会有所帮助:
从get
函数中获取这段代码:
if(umap.count(key))
// remove it from the old position
klist.erase(umap[key].second);
上面会在地图中查找key
两次。一次用于count
方法,看看它是否存在。另一个调用[]
运算符来获取它的值。这样做可以节省几个周期:
auto itor = umap.find(key);
if (itor != umap.end())
// remove it from the old position
klist.erase(itor->second);
在put
函数中,您可以这样做:
if(umap.count(key))
klist.erase(umap[key].second);
umap.erase(key);
同get
,可以通过umap
避免冗余搜索。此外,没有理由调用 umap.erase
只是为了在几行之后将相同的键添加回映射中。
另外,这也是低效的
umap[key].first = value;
umap[key].second = --key_loc;
与上面类似,在地图中重复查找 key
两次。在第一个赋值语句中,key
不在映射中,因此它默认构造一个新的值对事物。第二个任务是在地图中进行另一次查找。
让我们重组你的put
函数如下:
void put(int key, int value)
auto itor = umap.find(key);
bool reinsert = (itor != umap.end());
// if key already exists delete it from the klist only
if (reinsert)
klist.erase(umap[key].second);
else
// if the unordered map is at max capacity
if (umap.size() == cap)
umap.erase(klist.front());
klist.pop_front();
// finally update klist and umap
klist.push_back(key);
list<int>::iterator key_loc = klist.end();
auto endOfList = --key_loc;
if (reinsert)
itor->second.first = value;
itor->second.second = endOfList;
else
const pair<int, list<int>::iterator> itempair = value, endOfList ;
umap.emplace(key, itempair);
这可能是您使用std::list
所能达到的程度。 list
类型的缺点是,如果不先将现有节点删除然后再将其添加回来,就无法将现有节点从中间移动到前面(或后面)。这是更新列表的几个不需要的内存分配。可能的替代方法是您只需使用自己的双链表类型并自己手动修复 prev/next 指针。
【讨论】:
您可以使用std::list::splice 将同一列表中的节点移动到前面或后面。 @selbie 谢谢!!我尝试了您的建议,尽管代码改进了它现在运行时间为 172 毫秒而不是 184 毫秒,而且 umap.emplace(key,itempair) 与 umap[key] = itempair 有何不同。据我了解 unordered_map 插入和查找是 O(1) 。 @Blastfurnace 尝试使用 klist.splice(klist.end(),klist,it->second.second);给我 [ AddressSanitizer : alloc-dealloc-mismatch。 ] 这是将中间节点移到列表末尾的正确方法吗? @auxeon 看起来是正确的。除了this is the code 我在 LeetCode 上使用并被接受之外,我不在一个可以测试任何东西的地方。请注意,我正在拼接到前面,但拼接到后面也应该可以。【参考方案2】:这是我的解决方案,虽然它是 O(1),但它不是最快的实现 你能提供一些反馈,也许我该如何优化这个想法?谢谢!
我要在这里讨论 selbie 的观点:if(umap.count(key))
的每个实例都将搜索键,使用 umap[key]
是搜索的等效项。您可以通过单个 std::unordered_map::find()
操作分配一个指向键的迭代器来避免重复搜索。
selbie 已经给出了int get()
的搜索代码,这是void put()
的搜索代码:
auto it = umap.find(key);
if (it != umap.end())
klist.erase(it ->second);
umap.erase(key);
边箱:
由于缺少输入和输出工作,目前不适用于您的代码,但如果您使用 std::cin
和 std::cout
,您可以禁用 C 和 C++ 流之间的同步,并将 cout 中的 cin 解绑为优化:(它们默认绑定在一起)
// If your using cin/cout or I/O
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
【讨论】:
cin.tie
和 cout.tie
调用如何提高这个完全没有 I/O 的特定代码的性能?
@selbie 对! (我很快查了一下代码并假设了一个 cin/cout 的实例)已编辑以上是关于C++ LRU 缓存 - 需要有关如何提高速度的建议的主要内容,如果未能解决你的问题,请参考以下文章