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  * <http://shark-ml.org/>
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 
37 #include <shark/Core/Exception.h>
38 #include <boost/intrusive/list.hpp>
39 #include <vector>
40 
41 
42 namespace 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.
54 template<class T>
55 class LRUCache{
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  };
63 public:
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  }
200 private:
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