LRUCache.h
Go to the documentation of this file.
1//===========================================================================
2/*!
3 *
4 *
5 * \brief Cache implementing an Least-Recently-Used Strategy
6 *
7 *
8 *
9 * \author O. Krause
10 * \date 2013
11 *
12 *
13 * \par Copyright 1995-2017 Shark Development Team
14 *
15 * <BR><HR>
16 * This file is part of Shark.
17 * <https://shark-ml.github.io/Shark/>
18 *
19 * Shark is free software: you can redistribute it and/or modify
20 * it under the terms of the GNU Lesser General Public License as published
21 * by the Free Software Foundation, either version 3 of the License, or
22 * (at your option) any later version.
23 *
24 * Shark is distributed in the hope that it will be useful,
25 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 * GNU Lesser General Public License for more details.
28 *
29 * You should have received a copy of the GNU Lesser General Public License
30 * along with Shark. If not, see <http://www.gnu.org/licenses/>.
31 *
32 */
33//===========================================================================
34#ifndef SHARK_LINALG_LRUCACHE_H
35#define SHARK_LINALG_LRUCACHE_H
36
38#include <boost/intrusive/list.hpp>
39#include <vector>
40
41
42namespace shark{
43
44/// \brief Implements an LRU-Caching Strategy for arbitrary Cache-Lines.
45///
46/// Low Level Cache which stores cache lines, arrays of T[size] where size is a variable length for every cache line.
47/// Every line is associated with some index 0<i < max. It is assumed that not all cache lines can be
48/// stored at the same time, but only a (small) subset. The size of the cache is bounded by the summed length of all
49/// cache lines, that means that when the lines are very short, the cache can store more lines.
50/// If the cache is full and another line needs to be accessed, or an existing line needs to be resized,
51/// cache lines need to be freed. This cache uses an Least-Recently-Used strategy. The cache maintains
52/// a list. Everytime a cacheline is accessed, it moves to the front of the list. When a line is freed
53/// the end of the list is chosen.
54template<class T>
56 /// cache data held for every example
57 struct CacheEntry: public boost::intrusive::list_base_hook<>
58 {
59 T* data; ///< pointer to the beginning of the matrix row
60 std::size_t length; ///< length of the currently calculated strip of variables
61 CacheEntry():length(0){}
62 };
63public:
64 /// \brief Creates a cache with a given maximum index "lines" and a given maximum cache size.
65 LRUCache(std::size_t lines, std::size_t cachesize = 0x4000000)
66 : m_cacheEntry(lines)
67 , m_cacheSize( 0 )
68 , m_maxSize( cachesize ){}
69
71 clear();
72 }
73
74 ///\brief Returns true if the line is cached.
75 bool isCached(std::size_t i)const{
76 return m_cacheEntry[i].length != 0;
77 }
78 ///\brief Returns the size of the cached line.
79 std::size_t lineLength(std::size_t i)const{
80 return m_cacheEntry[i].length;
81 }
82
83 /// \brief Returns the number of lines currently allocated.
84 std::size_t cachedLines()const{
85 return m_lruList.size();
86 }
87
88 ///\brief Returns the line with index i with the correct size.
89 ///
90 ///If the line is not cached, it is created with the exact size. if it is cached
91 ///and is at least as big, it is returned unchanged. otherwise it is
92 ///resized to the proper size and the old values are kept.
93 T* getCacheLine(std::size_t i, std::size_t size){
94 CacheEntry& entry = m_cacheEntry[i];
95 //if the is cached, we push it to the front
96 if(!isCached(i))
97 cacheCreateRow(entry,size);
98 else{
99 if(entry.length >= size)
100 cacheRedeclareNewest(entry);
101 else
102 resizeLine(entry,size);
103 }
104 return entry.data;
105 }
106
107 ///\brief Just returns the pointer to the i-th line without affcting cache at all.
108 T* getLinePointer(std::size_t i){
109 return m_cacheEntry[i].data;
110 }
111
112 ///\brief Just returns the pointer to the i-th line without affcting cache at all.
113 T const* getLinePointer(std::size_t i)const{
114 return m_cacheEntry[i].data;
115 }
116
117 /// \brief Resizes a line while retaining the data stored inside it.
118 ///
119 /// if the new size is smaller than the old, only the first size entries are saved.
120 void resizeLine(std::size_t i ,std::size_t size){
121 resizeLine(m_cacheEntry[i],size);
122 }
123
124 ///\brief Marks cache line i for deletion, that is the next time memory is needed, this line will be freed.
125 void markLineForDeletion(std::size_t i){
126 if(!isCached(i)) return;
127 CacheEntry& block = m_cacheEntry[i];
128 m_lruList.erase(m_lruList.iterator_to(block));
129 m_lruList.push_back(block);
130 }
131
132 ///\brief swaps index of lines i and j.
133 void swapLineIndices(std::size_t i, std::size_t j){
134 typedef typename boost::intrusive::list<CacheEntry>::iterator Iterator;
135 //SHARK_ASSERT( i <= j );
136 //nothing to do if lines are not cached or indizes are the same
137 if( i == j || (!isCached(i) && !isCached(j))) return;
138
139 CacheEntry& cachei = m_cacheEntry[i];
140 CacheEntry& cachej = m_cacheEntry[j];
141
142 //correct list to point to the exchanged values
143 if(isCached(i) && !isCached(j)){
144 Iterator pos = m_lruList.iterator_to( cachei );
145 m_lruList.insert(pos,cachej);
146 m_lruList.erase(pos);
147 }else if(!isCached(i) && isCached(j)){
148 Iterator pos = m_lruList.iterator_to( cachej );
149 m_lruList.insert(pos,cachei);
150 m_lruList.erase(pos);
151 }else if(isCached(i) && isCached(j)){
152 Iterator posi = m_lruList.iterator_to( cachei );
153 Iterator posj = m_lruList.iterator_to( cachej );
154 //increment to the next position in the list so that we have
155 //a stable position in case we ned to remove one. Also note that insert(incposi,elem) now
156 //inserts directly before the position of elem i
157 Iterator incposi = posi;++incposi;
158 Iterator incposj = posj;++incposj;
159 //there is one important edge-case: that is the two elements are next in the list
160 //in this case, we can just remove and insert it before i again
161 if(incposi == posj){
162 m_lruList.erase( posj );
163 m_lruList.insert(posi,cachej);
164 } else if(incposj == posi){
165 m_lruList.erase( posi );
166 m_lruList.insert(posj,cachei);
167 }
168 else{
169 //erase elements, this does not affect the incremented iterators
170 m_lruList.erase( m_lruList.iterator_to( cachei ) );
171 m_lruList.erase( m_lruList.iterator_to( cachej ) );
172 //insert at correct positions
173 m_lruList.insert(incposi,cachej);
174 m_lruList.insert(incposj,cachei);
175 }
176 }
177
178 //exchange entries
179 std::swap(cachei.length,cachej.length);
180 std::swap(cachei.data,cachej.data);
181 }
182
183 std::size_t size()const{
184 return m_cacheSize;
185 }
186
187 std::size_t listIndex(std::size_t i)const{
188 typename boost::intrusive::list<CacheEntry>::const_iterator iter = m_lruList.begin();
189 std::advance(iter,i);
190 return &(*iter)-&m_cacheEntry[0];
191 }
192 std::size_t maxSize()const{
193 return m_maxSize;
194 }
195
196 ///\brief empty cache
197 void clear(){
198 ensureFreeMemory(m_maxSize);
199 }
200private:
201 /// \brief Pushes a cached entry to the bginning of the lru-list
202 void cacheRedeclareNewest(CacheEntry& block){
203 m_lruList.erase(m_lruList.iterator_to(block));
204 m_lruList.push_front(block);
205 }
206
207 ///\brief Creates a new row with a certain size > 0.
208 void cacheCreateRow(CacheEntry& block,std::size_t size){
209 SIZE_CHECK(size > 0);
210 ensureFreeMemory(size);
211 block.length = size;
212 block.data = new T[size];
213 m_lruList.push_front(block);
214 m_cacheSize += size;
215 }
216 /// \brief Removes a cached row.
217 void cacheRemoveRow(CacheEntry& block){
218 m_cacheSize -= block.length;
219 m_lruList.erase( m_lruList.iterator_to( block ) );
220 delete[] block.data;
221 block.length = 0;
222 }
223 /// \brief Resizes a line and copies all old values into it.
224 void resizeLine(CacheEntry& block,std::size_t size){
225
226 //salvage block data
227 T* newLine = new T[size];
228 std::copy(block.data,block.data+std::min(size,block.length),newLine);
229
230 //remove old data
231 cacheRemoveRow(block);
232 //free space for the new block
233 ensureFreeMemory(size);
234
235 //add new block
236 block.data = newLine;
237 block.length = size;
238 m_cacheSize += size;
239 m_lruList.push_front(block);
240 }
241
242 ///\brief Frees enough memory until a given amount of T can be allocated
243 void ensureFreeMemory(std::size_t size){
244 SIZE_CHECK(size <= m_maxSize);
245 while(m_maxSize-m_cacheSize < size){
246 cacheRemoveRow(m_lruList.back());//remove the oldest row
247 }
248 }
249
250 std::vector<CacheEntry> m_cacheEntry; ///< cache entry description
251 boost::intrusive::list<CacheEntry> m_lruList;
252
253 std::size_t m_cacheSize;//current size of cache in T
254 std::size_t m_maxSize;//maximum size of cache in T
255
256
257};
258}
259#endif