149e08aacStbbdev /* 2cb88c51cSIlya Isaev Copyright (c) 2005-2022 Intel Corporation 349e08aacStbbdev 449e08aacStbbdev Licensed under the Apache License, Version 2.0 (the "License"); 549e08aacStbbdev you may not use this file except in compliance with the License. 649e08aacStbbdev You may obtain a copy of the License at 749e08aacStbbdev 849e08aacStbbdev http://www.apache.org/licenses/LICENSE-2.0 949e08aacStbbdev 1049e08aacStbbdev Unless required by applicable law or agreed to in writing, software 1149e08aacStbbdev distributed under the License is distributed on an "AS IS" BASIS, 1249e08aacStbbdev WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1349e08aacStbbdev See the License for the specific language governing permissions and 1449e08aacStbbdev limitations under the License. 1549e08aacStbbdev */ 1649e08aacStbbdev 1749e08aacStbbdev #ifndef __TBB_concurrent_lru_cache_H 1849e08aacStbbdev #define __TBB_concurrent_lru_cache_H 1949e08aacStbbdev 2049e08aacStbbdev #if ! TBB_PREVIEW_CONCURRENT_LRU_CACHE 2149e08aacStbbdev #error Set TBB_PREVIEW_CONCURRENT_LRU_CACHE to include concurrent_lru_cache.h 2249e08aacStbbdev #endif 2349e08aacStbbdev 2449e08aacStbbdev #include "detail/_assert.h" 2549e08aacStbbdev #include "detail/_aggregator.h" 2649e08aacStbbdev 2749e08aacStbbdev #include <map> // for std::map 2849e08aacStbbdev #include <list> // for std::list 2949e08aacStbbdev #include <utility> // for std::make_pair 3049e08aacStbbdev #include <algorithm> // for std::find 3149e08aacStbbdev #include <atomic> // for std::atomic<bool> 3249e08aacStbbdev 3349e08aacStbbdev namespace tbb { 3449e08aacStbbdev 3549e08aacStbbdev namespace detail { 3649e08aacStbbdev namespace d1 { 3749e08aacStbbdev 3849e08aacStbbdev //----------------------------------------------------------------------------- 3949e08aacStbbdev // Concurrent LRU cache 4049e08aacStbbdev //----------------------------------------------------------------------------- 4149e08aacStbbdev 4249e08aacStbbdev template<typename KeyT, typename ValT, typename KeyToValFunctorT = ValT (*) (KeyT)> 4349e08aacStbbdev class concurrent_lru_cache : no_assign { 4449e08aacStbbdev // incapsulated helper classes 4549e08aacStbbdev private: 4649e08aacStbbdev struct handle_object; 4749e08aacStbbdev struct storage_map_value_type; 4849e08aacStbbdev 4949e08aacStbbdev struct aggregator_operation; 5049e08aacStbbdev struct retrieve_aggregator_operation; 5149e08aacStbbdev struct signal_end_of_usage_aggregator_operation; 5249e08aacStbbdev 5349e08aacStbbdev // typedefs 5449e08aacStbbdev public: 5549e08aacStbbdev using key_type = KeyT; 5649e08aacStbbdev using value_type = ValT; 5749e08aacStbbdev using pointer = ValT*; 5849e08aacStbbdev using reference = ValT&; 5949e08aacStbbdev using const_pointer = const ValT*; 6049e08aacStbbdev using const_reference = const ValT&; 6149e08aacStbbdev 6249e08aacStbbdev using value_function_type = KeyToValFunctorT; 6349e08aacStbbdev using handle = handle_object; 6449e08aacStbbdev private: 6549e08aacStbbdev using lru_cache_type = concurrent_lru_cache<KeyT, ValT, KeyToValFunctorT>; 6649e08aacStbbdev 6749e08aacStbbdev using storage_map_type = std::map<key_type, storage_map_value_type>; 6849e08aacStbbdev using storage_map_iterator_type = typename storage_map_type::iterator; 6949e08aacStbbdev using storage_map_pointer_type = typename storage_map_type::pointer; 7049e08aacStbbdev using storage_map_reference_type = typename storage_map_type::reference; 7149e08aacStbbdev 7249e08aacStbbdev using history_list_type = std::list<storage_map_iterator_type>; 7349e08aacStbbdev using history_list_iterator_type = typename history_list_type::iterator; 7449e08aacStbbdev 7549e08aacStbbdev using aggregator_operation_type = aggregator_operation; 7649e08aacStbbdev using aggregator_function_type = aggregating_functor<lru_cache_type, aggregator_operation_type>; 7749e08aacStbbdev using aggregator_type = aggregator<aggregator_function_type, aggregator_operation_type>; 7849e08aacStbbdev 7949e08aacStbbdev friend class aggregating_functor<lru_cache_type,aggregator_operation_type>; 8049e08aacStbbdev 8149e08aacStbbdev // fields 8249e08aacStbbdev private: 8349e08aacStbbdev value_function_type my_value_function; 8449e08aacStbbdev aggregator_type my_aggregator; 8549e08aacStbbdev 8649e08aacStbbdev storage_map_type my_storage_map; // storage map for used objects 8749e08aacStbbdev history_list_type my_history_list; // history list for unused objects 8849e08aacStbbdev const std::size_t my_history_list_capacity; // history list's allowed capacity 8949e08aacStbbdev 9049e08aacStbbdev // interface 9149e08aacStbbdev public: 9249e08aacStbbdev concurrent_lru_cache(value_function_type value_function,std::size_t cache_capacity)9349e08aacStbbdev concurrent_lru_cache(value_function_type value_function, std::size_t cache_capacity) 9449e08aacStbbdev : my_value_function(value_function), my_history_list_capacity(cache_capacity) { 9549e08aacStbbdev my_aggregator.initialize_handler(aggregator_function_type(this)); 9649e08aacStbbdev } 9749e08aacStbbdev 9849e08aacStbbdev handle operator[](key_type key) { 9949e08aacStbbdev retrieve_aggregator_operation op(key); 10049e08aacStbbdev my_aggregator.execute(&op); 10149e08aacStbbdev 10249e08aacStbbdev if (op.is_new_value_needed()) { 10349e08aacStbbdev op.result().second.my_value = my_value_function(key); 10449e08aacStbbdev op.result().second.my_is_ready.store(true, std::memory_order_release); 10549e08aacStbbdev } else { 10649e08aacStbbdev spin_wait_while_eq(op.result().second.my_is_ready, false); 10749e08aacStbbdev } 10849e08aacStbbdev 10949e08aacStbbdev return handle(*this, op.result()); 11049e08aacStbbdev } 11149e08aacStbbdev 11249e08aacStbbdev private: 11349e08aacStbbdev handle_operations(aggregator_operation * op_list)11449e08aacStbbdev void handle_operations(aggregator_operation* op_list) { 11549e08aacStbbdev while (op_list) { 11649e08aacStbbdev op_list->cast_and_handle(*this); 11749e08aacStbbdev aggregator_operation* prev_op = op_list; 11849e08aacStbbdev op_list = op_list->next; 11949e08aacStbbdev 12049e08aacStbbdev (prev_op->status).store(1, std::memory_order_release); 12149e08aacStbbdev } 12249e08aacStbbdev } 12349e08aacStbbdev signal_end_of_usage(storage_map_reference_type map_record_ref)12449e08aacStbbdev void signal_end_of_usage(storage_map_reference_type map_record_ref) { 12549e08aacStbbdev signal_end_of_usage_aggregator_operation op(map_record_ref); 12649e08aacStbbdev my_aggregator.execute(&op); 12749e08aacStbbdev } 12849e08aacStbbdev signal_end_of_usage_serial(storage_map_reference_type map_record_ref)12949e08aacStbbdev void signal_end_of_usage_serial(storage_map_reference_type map_record_ref) { 13049e08aacStbbdev storage_map_iterator_type map_it = my_storage_map.find(map_record_ref.first); 13149e08aacStbbdev 13249e08aacStbbdev __TBB_ASSERT(map_it != my_storage_map.end(), 13349e08aacStbbdev "cache should not return past-end iterators to outer world"); 13449e08aacStbbdev __TBB_ASSERT(&(*map_it) == &map_record_ref, 13549e08aacStbbdev "dangling reference has been returned to outside world: data race?"); 13649e08aacStbbdev __TBB_ASSERT(std::find(my_history_list.begin(), my_history_list.end(), map_it) == my_history_list.end(), 13749e08aacStbbdev "object in use should not be in list of unused objects "); 13849e08aacStbbdev 13949e08aacStbbdev // if it was the last reference, put it to the LRU history 14049e08aacStbbdev if (! --(map_it->second.my_ref_counter)) { 14149e08aacStbbdev // if the LRU history is full, evict the oldest items to get space 14249e08aacStbbdev if (my_history_list.size() >= my_history_list_capacity) { 143cb88c51cSIlya Isaev if (my_history_list_capacity == 0) { 144cb88c51cSIlya Isaev // Since LRU history capacity is zero, there is no need to keep the element in history 145cb88c51cSIlya Isaev my_storage_map.erase(map_it); 146cb88c51cSIlya Isaev return; 147cb88c51cSIlya Isaev } 14849e08aacStbbdev std::size_t number_of_elements_to_evict = 1 + my_history_list.size() - my_history_list_capacity; 14949e08aacStbbdev 15049e08aacStbbdev for (std::size_t i = 0; i < number_of_elements_to_evict; ++i) { 15149e08aacStbbdev storage_map_iterator_type map_it_to_evict = my_history_list.back(); 15249e08aacStbbdev 15349e08aacStbbdev __TBB_ASSERT(map_it_to_evict->second.my_ref_counter == 0, 15449e08aacStbbdev "item to be evicted should not have a live references"); 15549e08aacStbbdev 15649e08aacStbbdev // TODO: can we use forward_list instead of list? pop_front / insert_after last 15749e08aacStbbdev my_history_list.pop_back(); 15849e08aacStbbdev my_storage_map.erase(map_it_to_evict); 15949e08aacStbbdev } 16049e08aacStbbdev } 16149e08aacStbbdev 16249e08aacStbbdev // TODO: can we use forward_list instead of list? pop_front / insert_after last 16349e08aacStbbdev my_history_list.push_front(map_it); 16449e08aacStbbdev map_it->second.my_history_list_iterator = my_history_list.begin(); 16549e08aacStbbdev } 16649e08aacStbbdev } 16749e08aacStbbdev retrieve_serial(key_type key,bool & is_new_value_needed)16849e08aacStbbdev storage_map_reference_type retrieve_serial(key_type key, bool& is_new_value_needed) { 16949e08aacStbbdev storage_map_iterator_type map_it = my_storage_map.find(key); 17049e08aacStbbdev 17149e08aacStbbdev if (map_it == my_storage_map.end()) { 17249e08aacStbbdev map_it = my_storage_map.emplace_hint( 17349e08aacStbbdev map_it, std::piecewise_construct, std::make_tuple(key), std::make_tuple(value_type(), 0, my_history_list.end(), false)); 17449e08aacStbbdev is_new_value_needed = true; 17549e08aacStbbdev } else { 17649e08aacStbbdev history_list_iterator_type list_it = map_it->second.my_history_list_iterator; 17749e08aacStbbdev if (list_it != my_history_list.end()) { 17849e08aacStbbdev __TBB_ASSERT(map_it->second.my_ref_counter == 0, 17949e08aacStbbdev "item to be evicted should not have a live references"); 18049e08aacStbbdev 18149e08aacStbbdev // Item is going to be used. Therefore it is not a subject for eviction, 18249e08aacStbbdev // so we remove it from LRU history. 18349e08aacStbbdev my_history_list.erase(list_it); 18449e08aacStbbdev map_it->second.my_history_list_iterator = my_history_list.end(); 18549e08aacStbbdev } 18649e08aacStbbdev } 18749e08aacStbbdev 18849e08aacStbbdev ++(map_it->second.my_ref_counter); 18949e08aacStbbdev return *map_it; 19049e08aacStbbdev } 19149e08aacStbbdev }; 19249e08aacStbbdev 19349e08aacStbbdev //----------------------------------------------------------------------------- 19449e08aacStbbdev // Value type for storage map in concurrent LRU cache 19549e08aacStbbdev //----------------------------------------------------------------------------- 19649e08aacStbbdev 19749e08aacStbbdev template<typename KeyT, typename ValT, typename KeyToValFunctorT> 19849e08aacStbbdev struct concurrent_lru_cache<KeyT, ValT, KeyToValFunctorT>::storage_map_value_type { 19949e08aacStbbdev //typedefs 20049e08aacStbbdev public: 20149e08aacStbbdev using ref_counter_type = std::size_t; 20249e08aacStbbdev 20349e08aacStbbdev // fields 20449e08aacStbbdev public: 20549e08aacStbbdev value_type my_value; 20649e08aacStbbdev ref_counter_type my_ref_counter; 20749e08aacStbbdev history_list_iterator_type my_history_list_iterator; 20849e08aacStbbdev std::atomic<bool> my_is_ready; 20949e08aacStbbdev 21049e08aacStbbdev // interface 21149e08aacStbbdev public: 21249e08aacStbbdev storage_map_value_type( 21349e08aacStbbdev value_type const& value, ref_counter_type ref_counter, 21449e08aacStbbdev history_list_iterator_type history_list_iterator, bool is_ready) 21549e08aacStbbdev : my_value(value), my_ref_counter(ref_counter), 21649e08aacStbbdev my_history_list_iterator(history_list_iterator), my_is_ready(is_ready) {} 21749e08aacStbbdev }; 21849e08aacStbbdev 21949e08aacStbbdev //----------------------------------------------------------------------------- 22049e08aacStbbdev // Handle object for operator[] in concurrent LRU cache 22149e08aacStbbdev //----------------------------------------------------------------------------- 22249e08aacStbbdev 22349e08aacStbbdev template<typename KeyT, typename ValT, typename KeyToValFunctorT> 22449e08aacStbbdev struct concurrent_lru_cache<KeyT, ValT, KeyToValFunctorT>::handle_object { 22549e08aacStbbdev // fields 22649e08aacStbbdev private: 22749e08aacStbbdev lru_cache_type* my_lru_cache_ptr; 22849e08aacStbbdev storage_map_pointer_type my_map_record_ptr; 22949e08aacStbbdev 23049e08aacStbbdev // interface 23149e08aacStbbdev public: 23249e08aacStbbdev handle_object() 23349e08aacStbbdev : my_lru_cache_ptr(nullptr), my_map_record_ptr(nullptr) {} 23449e08aacStbbdev handle_object(lru_cache_type& lru_cache_ref, storage_map_reference_type map_record_ref) 23549e08aacStbbdev : my_lru_cache_ptr(&lru_cache_ref), my_map_record_ptr(&map_record_ref) {} 23649e08aacStbbdev 23749e08aacStbbdev handle_object(handle_object&) = delete; 23849e08aacStbbdev void operator=(handle_object&) = delete; 23949e08aacStbbdev 24049e08aacStbbdev handle_object(handle_object&& other) 24149e08aacStbbdev : my_lru_cache_ptr(other.my_lru_cache_ptr), my_map_record_ptr(other.my_map_record_ptr) { 24249e08aacStbbdev 24349e08aacStbbdev __TBB_ASSERT( 24455f9b178SIvan Kochin (other.my_lru_cache_ptr != nullptr && other.my_map_record_ptr != nullptr) || 24555f9b178SIvan Kochin (other.my_lru_cache_ptr == nullptr && other.my_map_record_ptr == nullptr), 24649e08aacStbbdev "invalid state of moving object?"); 24749e08aacStbbdev 24849e08aacStbbdev other.my_lru_cache_ptr = nullptr; 24949e08aacStbbdev other.my_map_record_ptr = nullptr; 25049e08aacStbbdev } 25149e08aacStbbdev 25249e08aacStbbdev handle_object& operator=(handle_object&& other) { 25349e08aacStbbdev __TBB_ASSERT( 25455f9b178SIvan Kochin (other.my_lru_cache_ptr != nullptr && other.my_map_record_ptr != nullptr) || 25555f9b178SIvan Kochin (other.my_lru_cache_ptr == nullptr && other.my_map_record_ptr == nullptr), 25649e08aacStbbdev "invalid state of moving object?"); 25749e08aacStbbdev 25849e08aacStbbdev if (my_lru_cache_ptr) 25949e08aacStbbdev my_lru_cache_ptr->signal_end_of_usage(*my_map_record_ptr); 26049e08aacStbbdev 26149e08aacStbbdev my_lru_cache_ptr = other.my_lru_cache_ptr; 26249e08aacStbbdev my_map_record_ptr = other.my_map_record_ptr; 26349e08aacStbbdev other.my_lru_cache_ptr = nullptr; 26449e08aacStbbdev other.my_map_record_ptr = nullptr; 26549e08aacStbbdev 26649e08aacStbbdev return *this; 26749e08aacStbbdev } 26849e08aacStbbdev 26949e08aacStbbdev ~handle_object() { 27049e08aacStbbdev if (my_lru_cache_ptr) 27149e08aacStbbdev my_lru_cache_ptr->signal_end_of_usage(*my_map_record_ptr); 27249e08aacStbbdev } 27349e08aacStbbdev 27449e08aacStbbdev operator bool() const { 27549e08aacStbbdev return (my_lru_cache_ptr && my_map_record_ptr); 27649e08aacStbbdev } 27749e08aacStbbdev 27849e08aacStbbdev value_type& value() { 27949e08aacStbbdev __TBB_ASSERT(my_lru_cache_ptr, "get value from already moved object?"); 28049e08aacStbbdev __TBB_ASSERT(my_map_record_ptr, "get value from an invalid or already moved object?"); 28149e08aacStbbdev 28249e08aacStbbdev return my_map_record_ptr->second.my_value; 28349e08aacStbbdev } 28449e08aacStbbdev }; 28549e08aacStbbdev 28649e08aacStbbdev //----------------------------------------------------------------------------- 28749e08aacStbbdev // Aggregator operation for aggregator type in concurrent LRU cache 28849e08aacStbbdev //----------------------------------------------------------------------------- 28949e08aacStbbdev 29049e08aacStbbdev template<typename KeyT, typename ValT, typename KeyToValFunctorT> 29149e08aacStbbdev struct concurrent_lru_cache<KeyT, ValT, KeyToValFunctorT>::aggregator_operation 29249e08aacStbbdev : aggregated_operation<aggregator_operation> { 29349e08aacStbbdev // incapsulated helper classes 29449e08aacStbbdev public: 29549e08aacStbbdev enum class op_type { retrieve, signal_end_of_usage }; 29649e08aacStbbdev 29749e08aacStbbdev // fields 29849e08aacStbbdev private: 29949e08aacStbbdev op_type my_op; 30049e08aacStbbdev 30149e08aacStbbdev // interface 30249e08aacStbbdev public: 30349e08aacStbbdev aggregator_operation(op_type op) : my_op(op) {} 30449e08aacStbbdev 30549e08aacStbbdev // TODO: aggregator_operation can be implemented 30649e08aacStbbdev // - as a statically typed variant type or CRTP? (static, dependent on the use case) 30749e08aacStbbdev // - or use pointer to function and apply_visitor (dynamic) 30849e08aacStbbdev // - or use virtual functions (dynamic) 30949e08aacStbbdev void cast_and_handle(lru_cache_type& lru_cache_ref) { 31049e08aacStbbdev if (my_op == op_type::retrieve) 31149e08aacStbbdev static_cast<retrieve_aggregator_operation*>(this)->handle(lru_cache_ref); 31249e08aacStbbdev else 31349e08aacStbbdev static_cast<signal_end_of_usage_aggregator_operation*>(this)->handle(lru_cache_ref); 31449e08aacStbbdev } 31549e08aacStbbdev }; 31649e08aacStbbdev 31749e08aacStbbdev template<typename KeyT, typename ValT, typename KeyToValFunctorT> 31849e08aacStbbdev struct concurrent_lru_cache<KeyT, ValT, KeyToValFunctorT>::retrieve_aggregator_operation 31949e08aacStbbdev : aggregator_operation, private no_assign { 32049e08aacStbbdev public: 32149e08aacStbbdev key_type my_key; 32249e08aacStbbdev storage_map_pointer_type my_map_record_ptr; 32349e08aacStbbdev bool my_is_new_value_needed; 32449e08aacStbbdev 32549e08aacStbbdev public: 32649e08aacStbbdev retrieve_aggregator_operation(key_type key) 32749e08aacStbbdev : aggregator_operation(aggregator_operation::op_type::retrieve), 328*f2af7473Skboyarinov my_key(key), my_map_record_ptr(nullptr), my_is_new_value_needed(false) {} 32949e08aacStbbdev 33049e08aacStbbdev void handle(lru_cache_type& lru_cache_ref) { 33149e08aacStbbdev my_map_record_ptr = &lru_cache_ref.retrieve_serial(my_key, my_is_new_value_needed); 33249e08aacStbbdev } 33349e08aacStbbdev 334*f2af7473Skboyarinov storage_map_reference_type result() { 335*f2af7473Skboyarinov __TBB_ASSERT(my_map_record_ptr, "Attempt to call result() before calling handle()"); 336*f2af7473Skboyarinov return *my_map_record_ptr; 337*f2af7473Skboyarinov } 33849e08aacStbbdev 33949e08aacStbbdev bool is_new_value_needed() { return my_is_new_value_needed; } 34049e08aacStbbdev }; 34149e08aacStbbdev 34249e08aacStbbdev template<typename KeyT, typename ValT, typename KeyToValFunctorT> 34349e08aacStbbdev struct concurrent_lru_cache<KeyT, ValT, KeyToValFunctorT>::signal_end_of_usage_aggregator_operation 34449e08aacStbbdev : aggregator_operation, private no_assign { 34549e08aacStbbdev 34649e08aacStbbdev private: 34749e08aacStbbdev storage_map_reference_type my_map_record_ref; 34849e08aacStbbdev 34949e08aacStbbdev public: 35049e08aacStbbdev signal_end_of_usage_aggregator_operation(storage_map_reference_type map_record_ref) 35149e08aacStbbdev : aggregator_operation(aggregator_operation::op_type::signal_end_of_usage), 35249e08aacStbbdev my_map_record_ref(map_record_ref) {} 35349e08aacStbbdev 35449e08aacStbbdev void handle(lru_cache_type& lru_cache_ref) { 35549e08aacStbbdev lru_cache_ref.signal_end_of_usage_serial(my_map_record_ref); 35649e08aacStbbdev } 35749e08aacStbbdev }; 35849e08aacStbbdev 35949e08aacStbbdev // TODO: if we have guarantees that KeyToValFunctorT always have 36049e08aacStbbdev // ValT as a return type and KeyT as an argument type 36149e08aacStbbdev // we can deduce template parameters of concurrent_lru_cache 36249e08aacStbbdev // by pattern matching on KeyToValFunctorT 36349e08aacStbbdev 36449e08aacStbbdev } // namespace d1 36549e08aacStbbdev } // namespace detail 36649e08aacStbbdev 36749e08aacStbbdev inline namespace v1 { 36849e08aacStbbdev 36949e08aacStbbdev using detail::d1::concurrent_lru_cache; 37049e08aacStbbdev 37149e08aacStbbdev } // inline namespace v1 37249e08aacStbbdev } // namespace tbb 37349e08aacStbbdev 37449e08aacStbbdev #endif // __TBB_concurrent_lru_cache_H 375