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