当前位置:   article > 正文

C++语言中LRU算法的一种实现_cpplru实现

cpplru实现

1.什么是LRU

以下内容来自百度百科:

      LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 t,当须淘汰一个页面时,选择现有页面中其 t 值最大的,即最近最少使用的页面予以淘汰。

     在实际项目开发中,为了提高程序运行速度,以空间换时间,常用的数据经常需要缓存,而LRU常用做一种的缓存更新策略被使用。本文就介绍C++语言下LRU的一种实现,希望对大家有帮助和启发。

2.模板 + 无序字典 + 双向链表实现LRU

数据缓存可能会存储各种各样的数据,如果为每种数据类型都写一个缓存算法,那代码量优点大,而且对后期维护也是一种很大的成本,在C++语言中模板编程可以很好的解决这个问题。缓存数据可多可少,支持动态变化,可以使用std::list结构存储缓存数据,缓存数据要支持快速查找,因此可以使用无须字典来快速访问std::list,字典key为缓存数据key,value为std::list的迭代器。

3.LRU类定义

lrucache.h

  1. #ifndef UTIL_LRU_CACHE_H
  2. #define UTIL_LRU_CACHE_H
  3. #include <list>
  4. #include <memory>
  5. #include <unordered_map>
  6. #include <mutex>
  7. #include <functional>
  8. /*
  9. //std::list方法splice使用举例
  10. list<string> list1, list2;
  11. list1.push_back("a");
  12. list1.push_back("b");
  13. list1.push_back("c"); // list1: "a", "b", "c"
  14. list<string>::iterator iterator1 = list1.begin();
  15. iterator1++: // points to "b"
  16. list2.push_back("x");
  17. list2.push_back("y");
  18. list2.push_back("z"); // list2: "x", "y", "z"
  19. list<string>::iterator iterator2 = list2.begin();
  20. iterator2++; // points to "y"
  21. list1.splice(iterator1, list2, iterator2, list2.end());
  22. // list1: "a", "y", "z", "b", "c"
  23. // list2: "x"
  24. */
  25. namespace common {
  26. namespace util {
  27. /// LRU cache base on elements count and memory size
  28. template <typename Key, typename Value>
  29. class LRUCache
  30. {
  31. public:
  32. using key_type = Key;
  33. using value_type = Value;
  34. using value_ptr_type = std::shared_ptr<Value>;
  35. using list_value = std::pair<Key, value_ptr_type>;
  36. using iterator = typename std::list<list_value>::iterator;
  37. using size_type = uint64_t;
  38. using rate_type = double;
  39. /// cache stat structure
  40. struct Stats {
  41. size_type m_get_cnt; // 总的请求次数
  42. size_type m_hit_cnt; // 命中次数
  43. size_type m_set_cnt; // 总设置次数
  44. };
  45. public:
  46. // if DropHandler is setup, lru droped data will be passed to DropHandler
  47. typedef std::function<
  48. void (const key_type&, const value_ptr_type&)> DropHandler;
  49. private:
  50. mutable std::mutex m_mutex;
  51. std::list<list_value> m_list;
  52. std::unordered_map<key_type, iterator> m_hash_table;
  53. size_type m_max_size; // 可保有的最大元素数量
  54. DropHandler m_drop_handler; // lru替换时执行的回调
  55. Stats m_stats;
  56. public:
  57. LRUCache() = delete;
  58. ///
  59. /// construct
  60. /// \param [max_size] 最大元素个数
  61. ///
  62. explicit LRUCache(size_type max_size);
  63. public:
  64. ///
  65. /// push
  66. /// \brief 将k-v对压入容器
  67. /// \param [in]: key, value
  68. /// \warning 压入后放在队头,若压入后满足了进行淘汰的条件,将淘汰1个元素
  69. ///
  70. void push(const key_type& key, const value_ptr_type& value);
  71. ///
  72. /// get
  73. /// \brief 根据key,从容器中取出value
  74. /// \param [in]: key, [out]: value
  75. /// \return bool [ture]: 容器中有此k-v对 [false]: 容器中无此k-v对
  76. /// \warning 若容器中有此k-v对,get操作会根据最近使用原则,将此k-v对移动至队头
  77. ///
  78. bool get(const key_type& key, value_ptr_type& value);
  79. ///
  80. /// exists
  81. /// \brief 判断容器中是否有key对应的k-v对
  82. /// \param [in]: key
  83. /// \return bool [ture]: 容器中有此k-v对 [false]: 容器中无此k-v对
  84. /// \warning 此方法不会影响元素位置,不更该容器状态,只返回k-v对是否存在
  85. ///
  86. bool exists(const key_type& key) const
  87. {
  88. std::lock_guard<std::mutex> lock (m_mutex);
  89. auto ite = m_hash_table.find(key);
  90. return ite == m_hash_table.end() ? false : true;
  91. }
  92. /// check drop handler
  93. /// \brief 判断lru替换数据回调是否存在
  94. bool check_drop_handler() const
  95. {
  96. std::lock_guard<std::mutex> lock (m_mutex);
  97. return m_drop_handler != nullptr;
  98. }
  99. /// get drop handler
  100. /// \brief 获取lru替换数据回调
  101. DropHandler get_drop_handler() const
  102. {
  103. std::lock_guard<std::mutex> lock (m_mutex);
  104. return m_drop_handler;
  105. }
  106. /// set drop handler
  107. /// \brief 设置lru替换数据回调
  108. void set_drop_handler(const DropHandler& handler)
  109. {
  110. std::lock_guard<std::mutex> lock (m_mutex);
  111. m_drop_handler = handler;
  112. }
  113. /// get_max_capacity
  114. /// \brief 获取当前最大内存限制量
  115. size_type get_max_capacity() const
  116. {
  117. std::lock_guard<std::mutex> lock (m_mutex);
  118. return m_max_size * sizeof(value_type);
  119. }
  120. /// get_current_capacity
  121. /// \brief 获取当前已经使用的内存量
  122. size_type get_current_capacity() const
  123. {
  124. std::lock_guard<std::mutex> lock (m_mutex);
  125. return _get_cache_size_in_lock() * sizeof(value_type);
  126. }
  127. /// get_current_count
  128. /// \brief 获取当前元素个数
  129. size_type get_current_count() const
  130. {
  131. std::lock_guard<std::mutex> lock (m_mutex);
  132. return _get_cache_size_in_lock();
  133. }
  134. ///
  135. /// get_hit_rate
  136. /// \brief 获取截至当前get的命中率
  137. /// \return rate_type(double)
  138. ///
  139. rate_type get_hit_rate() const
  140. {
  141. std::lock_guard<std::mutex> lock (m_mutex);
  142. return static_cast<rate_type>(m_stats.m_hit_cnt) / m_stats.m_get_cnt;
  143. }
  144. /// get_stats
  145. /// \brief 获取当前统计信息
  146. void get_stats(Stats& stats) const
  147. {
  148. std::lock_guard<std::mutex> lock (m_mutex);
  149. stats.m_hit_cnt = m_stats.m_hit_cnt;
  150. stats.m_get_cnt = m_stats.m_get_cnt;
  151. stats.m_set_cnt = m_stats.m_set_cnt;
  152. }
  153. ///
  154. /// reset_stats
  155. /// \brief 重置容器状态,重新统计命中率
  156. ///
  157. void reset_stats()
  158. {
  159. std::lock_guard<std::mutex> lock (m_mutex);
  160. m_stats.m_hit_cnt = 0;
  161. m_stats.m_get_cnt = 0;
  162. m_stats.m_set_cnt = 0;
  163. }
  164. private:
  165. ///
  166. /// [内部方法] 获取当前保有的元素个数
  167. /// \return size_type
  168. /// \warning 此方法必须在m_mutex上锁之后调用
  169. ///
  170. size_type _get_cache_size_in_lock() const
  171. {
  172. // 此处使用hashmap的size,时间复杂度为O(1)。
  173. // list求size的时间复杂度为O(n)
  174. return m_hash_table.size();
  175. }
  176. ///
  177. /// [内部方法] 淘汰一个元素,时间复杂度O(1)
  178. /// \warning 如果传入了淘汰回调,则需要同步等待回调执行完毕
  179. ///
  180. void _discard_one_element_in_lock()
  181. {
  182. // std::list为双向链表,end()的时间复杂度为O(1)
  183. auto ite_list_last = m_list.end();
  184. ite_list_last--;
  185. m_hash_table.erase(ite_list_last->first);
  186. // 如果设置了drop回调则需要执行drop回调
  187. if (m_drop_handler != nullptr) {
  188. m_drop_handler(ite_list_last->first, ite_list_last->second);
  189. }
  190. m_list.erase(ite_list_last);
  191. }
  192. };
  193. template <typename Key, typename Value>
  194. LRUCache<Key, Value>::LRUCache(size_type max_size) : m_max_size(max_size)
  195. {
  196. m_stats.m_get_cnt = 0;
  197. m_stats.m_set_cnt = 0;
  198. m_stats.m_hit_cnt = 0;
  199. }
  200. template <typename Key, typename Value>
  201. void LRUCache<Key, Value>::push(const key_type& key, const value_ptr_type& value) {
  202. std::lock_guard<std::mutex> lock (m_mutex);
  203. m_stats.m_set_cnt++;
  204. auto ite = m_hash_table.find(key);
  205. if (ite == m_hash_table.end()) {
  206. m_list.push_front({key, value});
  207. m_hash_table[key] = m_list.begin();
  208. } else {
  209. ite->second->second = value;
  210. m_list.splice(m_list.begin(), m_list, ite->second);
  211. }
  212. // 满足size条件才不会进行淘汰
  213. if (_get_cache_size_in_lock() > m_max_size) {
  214. _discard_one_element_in_lock();
  215. }
  216. }
  217. template <typename Key, typename Value>
  218. bool LRUCache<Key, Value>::get(const key_type& key, value_ptr_type& value)
  219. {
  220. std::lock_guard<std::mutex> lock (m_mutex);
  221. m_stats.m_get_cnt++;
  222. auto ite = m_hash_table.find(key);
  223. if (ite == m_hash_table.end()) {
  224. return false;
  225. }
  226. m_stats.m_hit_cnt++;
  227. m_list.splice(m_list.begin(), m_list, ite->second);
  228. value = ite->second->second;
  229. return true;
  230. }
  231. }
  232. }
  233. #endif

4.LRU类使用测试

  1. #include <iostream>
  2. #include <time.h>
  3. #include <sys/time.h>
  4. #include "lrucache.h"
  5. using namespace std;
  6. using namespace common::util;
  7. static inline uint64_t get_microsecond()
  8. {
  9. struct timeval tv;
  10. gettimeofday(&tv, NULL);
  11. uint64_t usec = tv.tv_sec * 1000000LLU + tv.tv_usec;
  12. return usec;
  13. }
  14. // 查找判断
  15. void test1() {
  16. int n = 4;
  17. LRUCache<int, int> lru0(30);
  18. for (int i = 0; i < n; ++i) {
  19. lru0.push(i, std::make_shared<int>(i + n));
  20. }
  21. cout << "===============================" << endl;
  22. for (int i = 0; i < n; ++i) {
  23. auto tmp = std::make_shared<int>(-1);
  24. lru0.get(i, tmp);
  25. std::cout << ((i + n) == *tmp) << std::endl;
  26. }
  27. cout << "===============================" << endl;
  28. for (int i = 0; i < n; ++i) {
  29. auto tmp = std::make_shared<int>(-1);
  30. lru0.get(i, tmp);
  31. std::cout << ((i + n) == *tmp) << std::endl;
  32. }
  33. cout << "===============================" << endl;
  34. cout << (0 == lru0.exists(n)) << endl;
  35. auto ret = std::make_shared<int>(-1);
  36. cout << (0 == lru0.get(n, ret)) << endl;
  37. }
  38. //根据元素个数进行淘汰
  39. void test2() {
  40. int n = 4;
  41. LRUCache<int, int> lru1(static_cast<uint64_t>(n));
  42. for (int i = 0; i < n; ++i) {
  43. lru1.push(i, std::make_shared<int>(i + n));
  44. }
  45. for (int i = 0; i < n; ++i) {
  46. auto tmp = std::make_shared<int>(-1);
  47. cout << (1 == lru1.get(i, tmp)) << endl;
  48. cout << (i + n == *tmp) << endl;
  49. }
  50. for (int i = n; i < n * 2; ++i) {
  51. lru1.push(i, std::make_shared<int>(i + n));
  52. cout << (1 == lru1.exists(i)) << endl;
  53. int j = 0;
  54. for (; j <= i - n; ++j) {
  55. cout << (1 == lru1.exists(j)) << endl;
  56. }
  57. for (; j <= i; ++j) {
  58. cout << (1 == lru1.exists(j)) << endl;
  59. }
  60. }
  61. }
  62. void test3() {
  63. const int cap = 3000000;
  64. LRUCache<int, int> lru4(static_cast<uint64_t>(cap));
  65. uint64_t totalTime = 0;
  66. for (int i = 0; i < cap; ++i) {
  67. uint64_t begin = get_microsecond();
  68. lru4.push(i, std::make_shared<int>(i));
  69. uint64_t end = get_microsecond();
  70. totalTime += end - begin;
  71. }
  72. std::cout << "average latency with no-discard push:"
  73. << 1.0 * totalTime / cap << " us" << std::endl;
  74. totalTime = 0;
  75. for (int i = cap; i < cap * 2; ++i) {
  76. uint64_t begin = get_microsecond();
  77. lru4.push(i, std::make_shared<int>(i));
  78. uint64_t end = get_microsecond();
  79. totalTime += end - begin;
  80. }
  81. std::cout << "average latency with discard push:"
  82. << 1.0 * totalTime / cap << " us" << std::endl;
  83. totalTime = 0;
  84. for (int i = cap; i < cap * 2; ++i) {
  85. auto tmp = std::make_shared<int>(-1);
  86. uint64_t begin = get_microsecond();
  87. lru4.get(i, tmp);
  88. uint64_t end = get_microsecond();
  89. totalTime += end - begin;
  90. }
  91. std::cout << "average latency with get exists elements:"
  92. << 1.0 * totalTime / cap << " us" << std::endl;
  93. totalTime = 0;
  94. for (int i = 0; i < cap; ++i) {
  95. auto tmp = std::make_shared<int>(-1);
  96. uint64_t begin = get_microsecond();
  97. lru4.get(i, tmp);
  98. uint64_t end = get_microsecond();
  99. totalTime += end - begin;
  100. }
  101. std::cout << "average latency with get not exists elements:"
  102. << 1.0 * totalTime / cap << " us" << std::endl;
  103. }
  104. int main() {
  105. test3();
  106. return 0;
  107. }

编译运行:

g++ main.cpp 

./a.out

运行结果如下:

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/163727
推荐阅读
相关标签
  

闽ICP备14008679号