concurrent_lru_cache.h 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. /*
  2. Copyright 2005-2013 Intel Corporation. All Rights Reserved.
  3. This file is part of Threading Building Blocks.
  4. Threading Building Blocks is free software; you can redistribute it
  5. and/or modify it under the terms of the GNU General Public License
  6. version 2 as published by the Free Software Foundation.
  7. Threading Building Blocks is distributed in the hope that it will be
  8. useful, but WITHOUT ANY WARRANTY; without even the implied warranty
  9. of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU General Public License for more details.
  11. You should have received a copy of the GNU General Public License
  12. along with Threading Building Blocks; if not, write to the Free Software
  13. Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  14. As a special exception, you may use this file as part of a free software
  15. library without restriction. Specifically, if other files instantiate
  16. templates or use macros or inline functions from this file, or you compile
  17. this file and link it with other files to produce an executable, this
  18. file does not by itself cause the resulting executable to be covered by
  19. the GNU General Public License. This exception does not however
  20. invalidate any other reasons why the executable file might be covered by
  21. the GNU General Public License.
  22. */
  23. #ifndef __TBB_concurrent_lru_cache_H
  24. #define __TBB_concurrent_lru_cache_H
  25. #if ! TBB_PREVIEW_CONCURRENT_LRU_CACHE
  26. #error Set TBB_PREVIEW_CONCURRENT_LRU_CACHE to include concurrent_lru_cache.h
  27. #endif
  28. #include <map>
  29. #include <list>
  30. #include "tbb_stddef.h"
  31. #include "atomic.h"
  32. #include "internal/_aggregator_impl.h"
  33. namespace tbb{
  34. namespace interface6 {
  35. template <typename key_type, typename value_type, typename value_functor_type = value_type (*)(key_type) >
  36. class concurrent_lru_cache : internal::no_assign{
  37. private:
  38. typedef concurrent_lru_cache self_type;
  39. typedef value_functor_type value_function_type;
  40. typedef std::size_t ref_counter_type;
  41. struct map_value_type;
  42. typedef std::map<key_type, map_value_type> map_storage_type;
  43. typedef std::list<typename map_storage_type::iterator> lru_list_type;
  44. struct map_value_type {
  45. value_type my_value;
  46. ref_counter_type my_ref_counter;
  47. typename lru_list_type::iterator my_lru_list_iterator;
  48. bool my_is_ready;
  49. map_value_type (value_type const& a_value, ref_counter_type a_ref_counter, typename lru_list_type::iterator a_lru_list_iterator, bool a_is_ready)
  50. : my_value(a_value), my_ref_counter(a_ref_counter), my_lru_list_iterator (a_lru_list_iterator), my_is_ready(a_is_ready)
  51. {}
  52. };
  53. class handle_object;
  54. struct aggregator_operation;
  55. typedef aggregator_operation aggregated_operation_type;
  56. typedef tbb::internal::aggregating_functor<self_type,aggregated_operation_type> aggregator_function_type;
  57. friend class tbb::internal::aggregating_functor<self_type,aggregated_operation_type>;
  58. typedef tbb::internal::aggregator<aggregator_function_type, aggregated_operation_type> aggregator_type;
  59. private:
  60. value_function_type my_value_function;
  61. std::size_t const my_number_of_lru_history_items;
  62. map_storage_type my_map_storage;
  63. lru_list_type my_lru_list;
  64. aggregator_type my_aggregator;
  65. public:
  66. typedef handle_object handle;
  67. public:
  68. concurrent_lru_cache(value_function_type f, std::size_t number_of_lru_history_items)
  69. : my_value_function(f),my_number_of_lru_history_items(number_of_lru_history_items)
  70. {
  71. my_aggregator.initialize_handler(aggregator_function_type(this));
  72. }
  73. handle_object operator[](key_type k){
  74. retrieve_aggregator_operation op(k);
  75. my_aggregator.execute(&op);
  76. if (op.is_new_value_needed()){
  77. op.result().second.my_value = my_value_function(k);
  78. __TBB_store_with_release(op.result().second.my_is_ready, true);
  79. }else{
  80. tbb::internal::spin_wait_while_eq(op.result().second.my_is_ready,false);
  81. }
  82. return handle_object(*this,op.result());
  83. }
  84. private:
  85. void signal_end_of_usage(typename map_storage_type::reference value_ref){
  86. signal_end_of_usage_aggregator_operation op(value_ref);
  87. my_aggregator.execute(&op);
  88. }
  89. private:
  90. struct handle_move_t:no_assign{
  91. concurrent_lru_cache & my_cache_ref;
  92. typename map_storage_type::reference my_map_record_ref;
  93. handle_move_t(concurrent_lru_cache & cache_ref, typename map_storage_type::reference value_ref):my_cache_ref(cache_ref),my_map_record_ref(value_ref) {};
  94. };
  95. class handle_object {
  96. concurrent_lru_cache * my_cache_pointer;
  97. typename map_storage_type::reference my_map_record_ref;
  98. public:
  99. handle_object(concurrent_lru_cache & cache_ref, typename map_storage_type::reference value_ref):my_cache_pointer(&cache_ref), my_map_record_ref(value_ref) {}
  100. handle_object(handle_move_t m):my_cache_pointer(&m.my_cache_ref), my_map_record_ref(m.my_map_record_ref){}
  101. operator handle_move_t(){ return move(*this);}
  102. value_type& value(){
  103. __TBB_ASSERT(my_cache_pointer,"get value from moved from object?");
  104. return my_map_record_ref.second.my_value;
  105. }
  106. ~handle_object(){
  107. if (my_cache_pointer){
  108. my_cache_pointer->signal_end_of_usage(my_map_record_ref);
  109. }
  110. }
  111. private:
  112. friend handle_move_t move(handle_object& h){
  113. return handle_object::move(h);
  114. }
  115. static handle_move_t move(handle_object& h){
  116. __TBB_ASSERT(h.my_cache_pointer,"move from the same object twice ?");
  117. concurrent_lru_cache * cache_pointer = NULL;
  118. std::swap(cache_pointer,h.my_cache_pointer);
  119. return handle_move_t(*cache_pointer,h.my_map_record_ref);
  120. }
  121. private:
  122. void operator=(handle_object&);
  123. #if __SUNPRO_CC
  124. // Presumably due to a compiler error, private copy constructor
  125. // breaks expressions like handle h = cache[key];
  126. public:
  127. #endif
  128. handle_object(handle_object &);
  129. };
  130. private:
  131. //TODO: looks like aggregator_operation is a perfect match for statically typed variant type
  132. struct aggregator_operation : tbb::internal::aggregated_operation<aggregator_operation>{
  133. enum e_op_type {op_retive, op_signal_end_of_usage};
  134. //TODO: try to use pointer to function apply_visitor here
  135. //TODO: try virtual functions and measure the difference
  136. e_op_type my_operation_type;
  137. aggregator_operation(e_op_type operation_type): my_operation_type(operation_type) {}
  138. void cast_and_handle(self_type& container ){
  139. if (my_operation_type==op_retive){
  140. static_cast<retrieve_aggregator_operation*>(this)->handle(container);
  141. }else{
  142. static_cast<signal_end_of_usage_aggregator_operation*>(this)->handle(container);
  143. }
  144. }
  145. };
  146. struct retrieve_aggregator_operation : aggregator_operation, private internal::no_assign {
  147. key_type my_key;
  148. typename map_storage_type::pointer my_result_map_record_pointer;
  149. bool my_is_new_value_needed;
  150. retrieve_aggregator_operation(key_type key):aggregator_operation(aggregator_operation::op_retive),my_key(key),my_is_new_value_needed(false){}
  151. void handle(self_type& container ){
  152. my_result_map_record_pointer = & container.retrieve_serial(my_key,my_is_new_value_needed);
  153. }
  154. typename map_storage_type::reference result(){ return * my_result_map_record_pointer; }
  155. bool is_new_value_needed(){return my_is_new_value_needed;}
  156. };
  157. struct signal_end_of_usage_aggregator_operation : aggregator_operation, private internal::no_assign {
  158. typename map_storage_type::reference my_map_record_ref;
  159. signal_end_of_usage_aggregator_operation(typename map_storage_type::reference map_record_ref):aggregator_operation(aggregator_operation::op_signal_end_of_usage),my_map_record_ref(map_record_ref){}
  160. void handle(self_type& container ){
  161. container.signal_end_of_usage_serial(my_map_record_ref);
  162. }
  163. };
  164. private:
  165. void handle_operations(aggregator_operation* op_list){
  166. while(op_list){
  167. op_list->cast_and_handle(*this);
  168. aggregator_operation* tmp = op_list;
  169. op_list=op_list->next;
  170. tbb::internal::itt_store_word_with_release(tmp->status, uintptr_t(1));
  171. }
  172. }
  173. private:
  174. typename map_storage_type::reference retrieve_serial(key_type k, bool& is_new_value_needed){
  175. typename map_storage_type::iterator it = my_map_storage.find(k);
  176. if (it == my_map_storage.end()){
  177. it = my_map_storage.insert(it,std::make_pair(k,map_value_type(value_type(),0,my_lru_list.end(),false)));
  178. is_new_value_needed = true;
  179. }else {
  180. typename lru_list_type::iterator list_it = it->second.my_lru_list_iterator;
  181. if (list_it!=my_lru_list.end()) {
  182. __TBB_ASSERT(!it->second.my_ref_counter,"item to be evicted should not have a live references");
  183. //item is going to be used. Therefore it is not a subject for eviction
  184. //so - remove it from LRU history.
  185. my_lru_list.erase(list_it);
  186. it->second.my_lru_list_iterator= my_lru_list.end();
  187. }
  188. }
  189. ++(it->second.my_ref_counter);
  190. return *it;
  191. }
  192. void signal_end_of_usage_serial(typename map_storage_type::reference map_record_ref){
  193. typename map_storage_type::iterator it = my_map_storage.find(map_record_ref.first);
  194. __TBB_ASSERT(it!=my_map_storage.end(),"cache should not return past-end iterators to outer world");
  195. __TBB_ASSERT(&(*it) == &map_record_ref,"dangling reference has been returned to outside world? data race ?");
  196. __TBB_ASSERT( my_lru_list.end()== std::find(my_lru_list.begin(),my_lru_list.end(),it),
  197. "object in use should not be in list of unused objects ");
  198. if (! --(it->second.my_ref_counter)){
  199. //it was the last reference so put it to the LRU history
  200. if (my_lru_list.size()>=my_number_of_lru_history_items){
  201. //evict items in order to get a space
  202. size_t number_of_elements_to_evict = 1 + my_lru_list.size() - my_number_of_lru_history_items;
  203. for (size_t i=0; i<number_of_elements_to_evict; ++i){
  204. typename map_storage_type::iterator it_to_evict = my_lru_list.back();
  205. __TBB_ASSERT(!it_to_evict->second.my_ref_counter,"item to be evicted should not have a live references");
  206. my_lru_list.pop_back();
  207. my_map_storage.erase(it_to_evict);
  208. }
  209. }
  210. my_lru_list.push_front(it);
  211. it->second.my_lru_list_iterator = my_lru_list.begin();
  212. }
  213. }
  214. };
  215. } // namespace interface6
  216. using interface6::concurrent_lru_cache;
  217. } // namespace tbb
  218. #endif //__TBB_concurrent_lru_cache_H