1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
|
// Copyright (C) 2004 Patrick Audley <paudley@blackcat.ca>
// 2006 Nathaniel Smith <njs@pobox.com>
//
// This program is made available under the GNU GPL version 2.0 or
// greater. See the accompanying file COPYING for details.
//
// This program is distributed WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE.
#ifndef __LRU_WRITEBACK_CACHE_HH__
#define __LRU_WRITEBACK_CACHE_HH__
#include <map>
#include <list>
#include "cache_logger.hh"
#include "sanity.hh"
#include "safe_map.hh"
template <typename T> struct WritebackCountfn
{
unsigned long operator () (T const & x)
{
return 1;
}
};
// for use in caches where objects never become dirty
template <typename Key, typename Data> struct NullManager
{
inline void writeout(Key const &, Data const &)
{
I(false);
}
};
/**
* @brief Template cache with an LRU removal policy.
* @class LRUWritebackCache
*
* @par
* This template creats a simple collection of key-value pairs that grows
* until the size specified at construction is reached and then begins
* discard the Least Recently Used element on each insertion.
*
* It also tracks a 'dirty set'. Any given item can be marked clean or dirty.
* Importantly, when a dirty item is discarded, a Manager object is first
* given the chance to write it out to disk. All managing of the dirty bit is
* done manually by calling code.
*
*/
// Manager is a concept with a writeout(Key, Data) method
template <typename Key, typename Data,
typename Sizefn = WritebackCountfn<Data>,
typename Manager = NullManager<Key, Data> >
class LRUWritebackCache
{
public:
/// Main cache storage typedef
typedef std::list< std::pair<Key, Data> > List;
/// Main cache iterator
typedef typename List::iterator List_Iter;
/// Index typedef
typedef std::map<Key, List_Iter> Map;
/// Index iterator
typedef typename Map::iterator Map_Iter;
private:
/// Main cache storage
List _list;
/// Cache storage index
Map _index;
/// Dirty list
std::set<Key> _dirty;
/// Manager
Manager _manager;
/// Maximum abstract size of the cache
unsigned long _max_size;
/// Current abstract size of the cache
unsigned long _curr_size;
/// Minimum number of items in the cache (overrides the abstract size limit)
unsigned long _min_items;
cache_logger logger;
public:
/** @brief Creates a cache that holds at most Size worth of elements.
* @param Size maximum size of cache
*/
LRUWritebackCache(const unsigned long Size,
const unsigned long items,
Manager manager,
std::string const & logname)
: _manager(manager), _max_size(Size), _curr_size(0),
_min_items(items),
logger(logname, _max_size)
{
}
// Also allow a default-instantiated manager, for using this as a pure LRU
// cache with no writeback.
LRUWritebackCache(const unsigned long Size,
const unsigned long items)
: _max_size(Size), _curr_size(0),
_min_items(items),
logger("", 0)
{
}
/// Destructor - cleans up both index and storage
~LRUWritebackCache()
{
I(_dirty.empty());
}
/** @brief Gets the current abstract size of the cache.
* @return current size
*/
inline unsigned long size(void) const
{
return _curr_size;
}
/** @brief Gets the maximum abstract size of the cache.
* @return maximum size
*/
inline unsigned long max_size(void) const
{
return _max_size;
}
/// Checks if all items are clean (this should be true before a SQL BEGIN)
bool all_clean()
{
return _dirty.empty();
}
/// Cleans all dirty items (do this before a SQL COMMIT)
void clean_all()
{
for (typename std::set<Key>::const_iterator i = _dirty.begin(); i != _dirty.end(); ++i)
this->_writeout(*i);
_dirty.clear();
}
/// Clears all storage and indices (do this at SQL ROLLBACK)
void clear_and_drop_writes()
{
_list.clear();
_index.clear();
_dirty.clear();
_curr_size = 0;
};
/// Mark an item as not needing to be written back (do this when writing an
/// alternative form of it to the db, e.g. a delta). No-op if the item was
/// already clean.
void mark_clean(Key const & key)
{
_dirty.erase(key);
}
/// Say if we're planning to write back an item (do this to figure out
/// whether you should be writing an alternative form of it to the db,
/// e.g. a delta).
bool is_dirty(Key const & key)
{
return (_dirty.find(key) != _dirty.end());
}
/** @brief Checks for the existence of a key in the cache.
* @param key to check for
* @return bool indicating whether or not the key was found.
*/
inline bool exists(Key const & key) const
{
bool e = _index.find(key) != _index.end();
logger.log_exists(e, _position(key), _index.size(), _curr_size);
return e;
}
/** @brief Touches a key in the Cache and makes it the most recently used.
* @param key to be touched
*/
inline void touch(Key const & key)
{
Map_Iter miter = _index.find(key);
logger.log_touch(miter != _index.end(), _position(key),
_index.size(), _curr_size);
this->_touch(miter);
}
/** @brief Fetches a copy of cache data.
* @param key to fetch data for
* @param data to fetch data into
* @param touch whether or not to touch the data
* @return whether or not data was filled in
*/
inline bool fetch(Key const & key, Data & data, bool touch = true)
{
Map_Iter miter = _index.find(key);
logger.log_fetch(miter != _index.end(), _position(key),
_index.size(), _curr_size);
if (miter == _index.end())
return false;
if (touch)
this->touch(key);
data = miter->second->second;
return true;
}
/** @brief Inserts a key-data pair into the cache and removes entries if neccessary.
* @param key object key for insertion
* @param data object data for insertion
* @note This function checks key existence and touches the key if it already exists.
*/
inline void insert_clean(Key const & key, const Data & data)
{
// A little sanity check -- if we were empty, then we should have
// been zero-size:
if (_list.empty())
I(_curr_size == 0);
// Ok, do the actual insert at the head of the list
_list.push_front(std::make_pair(key, data));
List_Iter liter = _list.begin();
// Store the index
safe_insert(_index, std::make_pair(key, liter));
_curr_size += Sizefn()(data);
// Check to see if we need to remove an element due to exceeding max_size
int num_removed = 0;
unsigned long curr_size = _list.size();
while (_curr_size > _max_size && curr_size > _min_items)
{
// Remove the last element.
liter = _list.end();
I(liter != _list.begin());
--liter;
// liter now points to the last element. If the last element is also
// the first element -- i.e., the list has only one element, and we
// know that it's the one we just inserted -- then never mind, we
// never want to empty ourselves out completely.
if (liter == _list.begin())
break;
this->_remove(liter->first);
++num_removed;
--curr_size;
I(curr_size > 0);
}
I(exists(key));
logger.log_insert(num_removed, _index.size(), _curr_size);
}
inline void insert_dirty(Key const & key, const Data & data)
{
insert_clean(key, data);
safe_insert(_dirty, key);
I(is_dirty(key));
}
private:
/** @brief Internal touch function.
* @param key to be touched
* @return a Map_Iter pointing to the key that was touched.
*/
inline Map_Iter _touch(Map_Iter & miter)
{
if (miter == _index.end())
return miter;
// Move the found node to the head of the list.
_list.splice(_list.begin(), _list, miter->second);
return miter;
}
/** @brief Interal remove function
*/
inline void _remove(const Key & key)
{
if (_dirty.find(key) != _dirty.end())
{
this->_writeout(key);
safe_erase(_dirty, key);
}
Map_Iter miter = _index.find(key);
I(miter != _index.end());
_curr_size -= Sizefn()(miter->second->second);
_list.erase(miter->second);
_index.erase(miter);
}
// NB: does _not_ remove 'key' from the dirty set
inline void _writeout(Key const & key)
{
List_Iter const & i = safe_get(_index, key);
_manager.writeout(i->first, i->second);
}
// Determine where in the cache a given item is,
// for use with effectiveness logging.
inline int _position(Key const & key) const
{
if (!logger.logging())
{
return -2;
}
int pos = 0;
for (typename List::const_iterator liter = _list.begin();
liter != _list.end(); ++liter)
{
if (liter->first == key)
return pos;
++pos;
}
return -1;
}
};
#endif // __LRU_WRITEBACK_CACHE_HH__
// Local Variables:
// mode: C++
// fill-column: 76
// c-file-style: "gnu"
// indent-tabs-mode: nil
// End:
// vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s:
|