//===========================================================================
/*!
*
*
* \brief Cache implementing an Least-Recently-Used Strategy
*
*
*
* \author O. Krause
* \date 2013
*
*
* \par Copyright 1995-2017 Shark Development Team
*
*
* This file is part of Shark.
*
*
* Shark is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Shark is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Shark. If not, see .
*
*/
//===========================================================================
#ifndef SHARK_LINALG_LRUCACHE_H
#define SHARK_LINALG_LRUCACHE_H
#include
#include
#include
namespace shark{
/// \brief Implements an LRU-Caching Strategy for arbitrary Cache-Lines.
///
/// Low Level Cache which stores cache lines, arrays of T[size] where size is a variable length for every cache line.
/// Every line is associated with some index 0
class LRUCache{
/// cache data held for every example
struct CacheEntry: public boost::intrusive::list_base_hook<>
{
T* data; ///< pointer to the beginning of the matrix row
std::size_t length; ///< length of the currently calculated strip of variables
CacheEntry():length(0){}
};
public:
/// \brief Creates a cache with a given maximum index "lines" and a given maximum cache size.
LRUCache(std::size_t lines, std::size_t cachesize = 0x4000000)
: m_cacheEntry(lines)
, m_cacheSize( 0 )
, m_maxSize( cachesize ){}
~LRUCache(){
clear();
}
///\brief Returns true if the line is cached.
bool isCached(std::size_t i)const{
return m_cacheEntry[i].length != 0;
}
///\brief Returns the size of the cached line.
std::size_t lineLength(std::size_t i)const{
return m_cacheEntry[i].length;
}
/// \brief Returns the number of lines currently allocated.
std::size_t cachedLines()const{
return m_lruList.size();
}
///\brief Returns the line with index i with the correct size.
///
///If the line is not cached, it is created with the exact size. if it is cached
///and is at least as big, it is returned unchanged. otherwise it is
///resized to the proper size and the old values are kept.
T* getCacheLine(std::size_t i, std::size_t size){
CacheEntry& entry = m_cacheEntry[i];
//if the is cached, we push it to the front
if(!isCached(i))
cacheCreateRow(entry,size);
else{
if(entry.length >= size)
cacheRedeclareNewest(entry);
else
resizeLine(entry,size);
}
return entry.data;
}
///\brief Just returns the pointer to the i-th line without affcting cache at all.
T* getLinePointer(std::size_t i){
return m_cacheEntry[i].data;
}
///\brief Just returns the pointer to the i-th line without affcting cache at all.
T const* getLinePointer(std::size_t i)const{
return m_cacheEntry[i].data;
}
/// \brief Resizes a line while retaining the data stored inside it.
///
/// if the new size is smaller than the old, only the first size entries are saved.
void resizeLine(std::size_t i ,std::size_t size){
resizeLine(m_cacheEntry[i],size);
}
///\brief Marks cache line i for deletion, that is the next time memory is needed, this line will be freed.
void markLineForDeletion(std::size_t i){
if(!isCached(i)) return;
CacheEntry& block = m_cacheEntry[i];
m_lruList.erase(m_lruList.iterator_to(block));
m_lruList.push_back(block);
}
///\brief swaps index of lines i and j.
void swapLineIndices(std::size_t i, std::size_t j){
typedef typename boost::intrusive::list::iterator Iterator;
//SHARK_ASSERT( i <= j );
//nothing to do if lines are not cached or indizes are the same
if( i == j || (!isCached(i) && !isCached(j))) return;
CacheEntry& cachei = m_cacheEntry[i];
CacheEntry& cachej = m_cacheEntry[j];
//correct list to point to the exchanged values
if(isCached(i) && !isCached(j)){
Iterator pos = m_lruList.iterator_to( cachei );
m_lruList.insert(pos,cachej);
m_lruList.erase(pos);
}else if(!isCached(i) && isCached(j)){
Iterator pos = m_lruList.iterator_to( cachej );
m_lruList.insert(pos,cachei);
m_lruList.erase(pos);
}else if(isCached(i) && isCached(j)){
Iterator posi = m_lruList.iterator_to( cachei );
Iterator posj = m_lruList.iterator_to( cachej );
//increment to the next position in the list so that we have
//a stable position in case we ned to remove one. Also note that insert(incposi,elem) now
//inserts directly before the position of elem i
Iterator incposi = posi;++incposi;
Iterator incposj = posj;++incposj;
//there is one important edge-case: that is the two elements are next in the list
//in this case, we can just remove and insert it before i again
if(incposi == posj){
m_lruList.erase( posj );
m_lruList.insert(posi,cachej);
} else if(incposj == posi){
m_lruList.erase( posi );
m_lruList.insert(posj,cachei);
}
else{
//erase elements, this does not affect the incremented iterators
m_lruList.erase( m_lruList.iterator_to( cachei ) );
m_lruList.erase( m_lruList.iterator_to( cachej ) );
//insert at correct positions
m_lruList.insert(incposi,cachej);
m_lruList.insert(incposj,cachei);
}
}
//exchange entries
std::swap(cachei.length,cachej.length);
std::swap(cachei.data,cachej.data);
}
std::size_t size()const{
return m_cacheSize;
}
std::size_t listIndex(std::size_t i)const{
typename boost::intrusive::list::const_iterator iter = m_lruList.begin();
std::advance(iter,i);
return &(*iter)-&m_cacheEntry[0];
}
std::size_t maxSize()const{
return m_maxSize;
}
///\brief empty cache
void clear(){
ensureFreeMemory(m_maxSize);
}
private:
/// \brief Pushes a cached entry to the bginning of the lru-list
void cacheRedeclareNewest(CacheEntry& block){
m_lruList.erase(m_lruList.iterator_to(block));
m_lruList.push_front(block);
}
///\brief Creates a new row with a certain size > 0.
void cacheCreateRow(CacheEntry& block,std::size_t size){
SIZE_CHECK(size > 0);
ensureFreeMemory(size);
block.length = size;
block.data = new T[size];
m_lruList.push_front(block);
m_cacheSize += size;
}
/// \brief Removes a cached row.
void cacheRemoveRow(CacheEntry& block){
m_cacheSize -= block.length;
m_lruList.erase( m_lruList.iterator_to( block ) );
delete[] block.data;
block.length = 0;
}
/// \brief Resizes a line and copies all old values into it.
void resizeLine(CacheEntry& block,std::size_t size){
//salvage block data
T* newLine = new T[size];
std::copy(block.data,block.data+std::min(size,block.length),newLine);
//remove old data
cacheRemoveRow(block);
//free space for the new block
ensureFreeMemory(size);
//add new block
block.data = newLine;
block.length = size;
m_cacheSize += size;
m_lruList.push_front(block);
}
///\brief Frees enough memory until a given amount of T can be allocated
void ensureFreeMemory(std::size_t size){
SIZE_CHECK(size <= m_maxSize);
while(m_maxSize-m_cacheSize < size){
cacheRemoveRow(m_lruList.back());//remove the oldest row
}
}
std::vector m_cacheEntry; ///< cache entry description
boost::intrusive::list m_lruList;
std::size_t m_cacheSize;//current size of cache in T
std::size_t m_maxSize;//maximum size of cache in T
};
}
#endif