libjmmcg  release_579_6_g8cffd
A C++ library containing an eclectic mix of useful, advanced components.
cache.hpp
Go to the documentation of this file.
1 /******************************************************************************
2 ** Copyright © 2005 by J.M.McGuiness, coder@hussar.me.uk
3 **
4 ** This library is free software; you can redistribute it and/or
5 ** modify it under the terms of the GNU Lesser General Public
6 ** License as published by the Free Software Foundation; either
7 ** version 2.1 of the License, or (at your option) any later version.
8 **
9 ** This library is distributed in the hope that it will be useful,
10 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
11 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 ** Lesser General Public License for more details.
13 **
14 ** You should have received a copy of the GNU Lesser General Public
15 ** License along with this library; if not, write to the Free Software
16 ** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 */
18 
22 
23 #include <limits>
24 #include <map>
25 #include <time.h>
26 
27 /** \file
28  This file declares the various object that comprise the cache namespace.
29 */
30 
31 namespace jmmcg { namespace LIBJMMCG_VER_NAMESPACE { namespace cache {
32 
33  namespace private_ {
34  template<typename Mdl> struct multi_or_sequential {
35  static constexpr enum ppd::pool_traits::size_mode_t infinite_size_type=ppd::pool_traits::size_mode_t::infinite; ///< We allow an arbitrarily large amount of work to be transferred.
36  static constexpr enum ppd::pool_traits::size_mode_t fixed_size_type=ppd::pool_traits::size_mode_t::fixed_size; ///< Specifies that the thread pool is finite in size.
37  };
38  template<> struct multi_or_sequential<ppd::sequential_mode> {
41  };
42  }
43 
44  /// Adaptors for the single & multi-threading traits.
45  template<typename Key, typename Val, class Comp=std::less<Key>, class Alloc=std::allocator<Val> >
46  struct threading {
47  /// The sequential trait.
48  template<ppd::generic_traits::api_type API>
49  struct single {
50  /// Use a standard map as the basic associative collection.
51  typedef std::map<Key, Val, Comp, Alloc> assoc_colln_t;
53 
54  /// A sequential trait for executing work from which we do not need to get the results.
55  /**
56  For compatibility with the multi-threaded version.
57  */
58  typedef ppd::thread_pool<
59  ppd::pool_traits::work_distribution_mode_t::one_thread_distributes<>, ///< The master thread distributes the work.
60  ppd::pool_traits::size_mode_t::sequential, ///< This parameter specifies that it is sequential.
62  ppd::generic_traits::return_data::joinable, ///< This parameter specifies that we may be interested in any results.
63  API,
64  ppd::sequential_mode, ///< This parameter specifies that it is sequential.
66  >
68 
69  /// A sequential trait for executing work from which we want to get the results.
70  /**
71  For compatibility with the multi-threaded version.
72  */
73  typedef ppd::thread_pool<
74  ppd::pool_traits::work_distribution_mode_t::worker_threads_get_work<ppd::pool_traits::work_distribution_mode_t::queue_model_t::pool_owns_queue>, ///< The underlying "thread pool" uses a work-stealing model.
75  ppd::pool_traits::size_mode_t::sequential, ///< This parameter specifies that it is sequential.
77  ppd::generic_traits::return_data::joinable, ///< This parameter specifies that we may be interested in any results.
78  API,
79  ppd::sequential_mode, ///< This parameter specifies that it is sequential.
81  >
83  };
84 
85  /// The multi-threaded trait.
86  template<ppd::generic_traits::api_type API, typename Mdl>
87  struct multi {
88  /// Use a standard map as the basic associative collection.
89  /**
90  An associative collection that supported locking on its methods would avoid the gross locks required in the cache itself.
91  */
92  typedef std::map<Key,Val,Comp,Alloc> assoc_colln_t;
95 
96  /// A multi-threaded trait for executing work from which we do not need to get the results.
97  typedef typename ppd::thread_pool<
98  ppd::pool_traits::work_distribution_mode_t::one_thread_distributes<>, ///< The master thread distributes the work.
100  ppd::pool_aspects<
101  ppd::generic_traits::return_data::joinable, ///< This parameter specifies that we may be interested in any results.
102  API,
103  Mdl,
105  std::less
106  >
108 
109  /// A multi-threaded trait for executing work from which we want to get the results.
110  /**
111  Note that this is a finite-sized thread pool.
112  */
113  typedef typename ppd::thread_pool<
116  ppd::pool_aspects<
117  ppd::generic_traits::return_data::joinable, ///< This parameter specifies that we may be interested in any results.
118  API,
119  Mdl,
121  >
123  };
124  };
125 
126  /// A simple factory class that allows us to tell the cache how a value is made for a particular key.
127  template<typename Key, typename Data>
128  struct factory_base {
129  typedef Key key_type;
130  typedef Data value_type;
131 
132  virtual value_type __fastcall make(const key_type &) const=0;
133  virtual ~factory_base() {}
134  };
135 
136  /// An implementation of an Least Recently Used (LRU) algorithm for selecting victim entries in the cache for flushing.
137  template<typename Val>
138  class lru {
139  public:
140  typedef Val value_type;
141 
142  class criterion_t {
143  public:
144  explicit constexpr __stdcall criterion_t(const clock_t c) noexcept(true)
145  : oldest(c) {
146  }
147 
148  void operator=(criterion_t const &)=delete;
149 
150  template<class MapT_> bool __fastcall
151  operator()(const MapT_ &val) const noexcept(true) {
152  return val.second.accessed()<=oldest;
153  }
154 
155  private:
156  const clock_t oldest;
157  };
158 
159  constexpr __stdcall lru(void);
160  explicit constexpr __stdcall lru(const value_type &);
161  __stdcall lru(const lru &);
162  __stdcall ~lru(void);
163  lru & __fastcall operator=(const lru &);
164 
165  const value_type & __fastcall value(void) const noexcept(true);
166  value_type & __fastcall value(void) noexcept(true);
167 
168  clock_t __fastcall accessed(void) const noexcept(true);
169 
170  template<typename Iter> static const criterion_t __fastcall
171  select(Iter beg,const Iter &end) {
172  clock_t oldest=std::numeric_limits<clock_t>::max();
173  while (beg!=end) {
174  oldest=std::min(oldest,beg++->second.accessed());
175  }
176  return criterion_t(oldest);
177  }
178 
179  private:
180  mutable clock_t age;
181  value_type range_value;
182  };
183 
184  /// A trivial "no statistics" class.
185  struct no_statistics {
186  typedef unsigned long accesses_type;
187  typedef unsigned long hits_type;
188  typedef unsigned long flushes_type;
189  typedef unsigned long creations_type;
190 
191  static constexpr accesses_type __fastcall accesses() noexcept(true) {
192  return 0;
193  }
194  static constexpr hits_type __fastcall hits() noexcept(true) {
195  return 0;
196  }
197  static constexpr flushes_type __fastcall flushes() noexcept(true) {
198  return 0;
199  }
200  static constexpr creations_type __fastcall outstanding_fills() noexcept(true) {
201  return 0;
202  }
203  friend constexpr tostream &operator<<(tostream &os, const no_statistics &) noexcept(true) {
204  return os;
205  }
206 
207  static void __fastcall accessed(void) noexcept(true) {}
208  static void __fastcall hit(void) noexcept(true) {}
209  static void __fastcall reset(void) noexcept(true) {}
210  static void __fastcall flushed(void) noexcept(true) {}
211  };
212 
213  /// Some basic statistic gathering.
215  public:
216  typedef unsigned long accesses_type;
217  typedef unsigned long hits_type;
218  typedef unsigned long flushes_type;
219  typedef unsigned long creations_type;
220 
221  constexpr __stdcall basic_statistics(void) noexcept(true);
222  __stdcall basic_statistics(const basic_statistics &) noexcept(true);
223  virtual __stdcall ~basic_statistics(void) noexcept(true);
224  basic_statistics & __fastcall operator=(const basic_statistics &) noexcept(true);
225 
226  /// Total number of accesses to the cache.
227  accesses_type __fastcall accesses(void) const noexcept(true);
228  /// Total number of hits into the cache.
229  hits_type __fastcall hits(void) const noexcept(true);
230  /// Total number of cache flushes.
231  flushes_type __fastcall flushes(void) const noexcept(true);
232  /// Total number of outstanding cache fills.
233  /**
234  More useful for the multi-threaded cache to help tune the cache high- and low-water marks
235  and pool size for creating range-data the cache.
236  */
237  creations_type __fastcall outstanding_fills(void) const noexcept(true);
238 
239  /// Write all of the statistics to an output stream, in a formatted manner.
240  /**
241  \param os The output stream, templated for narrow or wide char use, based upon the definition or not of the preprocessor constant JMMCG_WIDE_CHARS. Idf not defined, standard "char" traits are used, otherwise "wchar_t" traits.
242  \param stats The basic_statistics object that should be written.
243  \return A reference to that "std::ostream" that was passed in.
244  */
245  friend tostream &operator<<(tostream &os, const basic_statistics &stats);
246 
247  /// Increment the "accessed" counter by 1.
248  void __fastcall accessed(void) noexcept(true);
249  /// Increment the "total hits" counter by 1.
250  void __fastcall hit(void) noexcept(true);
251  /// Increment the "total flushes" counter by 1.
252  void __fastcall flushed(void) noexcept(true);
253  /// Reset all of the counters.
254  void __fastcall reset(void) noexcept(true);
255 
256  private:
257  accesses_type total_accesses_;
258  hits_type total_hits_;
259  flushes_type total_flushes_;
260  };
261 
262  /// A base cache class, for deriving from to provide the real functionality.
263  /**
264  This class provides a low-level interface for the various cache types that can be
265  declared to allow some sort of compatibility and flexibility between them.
266  It also contains the factory, that identifies the basic properties of a cache:
267  the key and value types. Flush policies, statistics and threading are all extra
268  sugar, to some extent.
269  */
270  template<
271  class Factory
272  >
273  class base {
274  public:
275  typedef Factory factory_type; ///< The factory that is used to create a range-data value for a particular key.
276  typedef typename factory_type::key_type key_type; ///< The type of the key.
277  typedef typename factory_type::value_type value_type; ///< The type of the range-data.
278 
279  __stdcall base(const base &);
280  virtual __stdcall ~base(void) noexcept(true);
281 
282  base & __fastcall operator=(const base &);
283 
284  /// Returns "true" iff the internal, associative collection is empty.
285  virtual bool __fastcall empty() const noexcept(true)=0;
286  /// This function will reset any statistics upon clearing all contents from the internal, associative collection.
287  virtual void __fastcall clear()=0;
288 
289  /// Flush the contents of the cache according to the flush policy.
290  /**
291  The flush policy must be specified by a deriving class. The direct call to "flush" is run on the same thread, not another thread wrt the caller.
292  */
293  virtual void __fastcall flush()=0;
294 
295  protected:
296  struct range_data {
298  };
299  /// This class creates the data for a particular key, using the supplied factory.
301  public:
303 
304  explicit __stdcall find_range_data(const factory_type &f,const key_type &i)
305  : data_factory_(f),id(i) {
306  }
307  __stdcall find_range_data(const find_range_data &gw) noexcept(true)
308  : data_factory_(gw.data_factory_),id(gw.id) {
309  }
311  }
312 
313  void operator=(find_range_data const &)=delete;
314 
315  void __fastcall process(range_data &res) {
316  res.data=data_factory_.make(id);
317  }
318 
319  bool __fastcall operator<(find_range_data const &) const noexcept(true) {
320  return true;
321  }
322 
323  private:
324  const factory_type &data_factory_;
325  const key_type id;
326  };
327 
328  /// This class flushes the internal associative collection of the cache according to the flush policy.
329  class flush_cache {
330  public:
331  typedef void result_type;
332 
333  explicit __stdcall flush_cache(base<Factory> &r) noexcept(true)
334  : the_cache(r) {
335  }
336  __stdcall flush_cache(const flush_cache &r) noexcept(true)
337  : the_cache(r.the_cache) {
338  }
340  }
341 
342  void operator=(flush_cache const &)=delete;
343 
344  void __fastcall process() {
345  the_cache.flush();
346  }
347 
348  bool __fastcall operator<(flush_cache const &) const noexcept(true) {
349  return true;
350  }
351 
352  private:
353  base<Factory> &the_cache;
354  };
355 
356  explicit __stdcall base(const factory_type &f)
357  : data_factory_(f) {
358  }
359 
360  const factory_type & __fastcall data_factory() const noexcept(true);
361 
362  private:
363  factory_type data_factory_;
364  };
365 
366  /// A read-only cache that returns a const reference to the range data.
367  /**
368  The cache is a value-type, so you can have caches of caches.
369  */
370  template<
371  class Factory,
372  class FP=lru<typename Factory::value_type>, ///< We default to an LRU flush policy.
373  class ThrT=typename threading<typename Factory::key_type,FP>::template single<ppd::platform_api>, ///< We default to a sequential policy.
374  class Stats=no_statistics ///< We default to not collecting any statistics.
375  >
376  class ro : public base<Factory> {
377  public:
378  typedef base<Factory>base_t;
379  typedef typename base_t::factory_type factory_type; ///< The actual factory type.
380  typedef typename base_t::key_type key_type; ///< The type of the key.
381  typedef ThrT thread_traits; ///< The type of threading in force.
382  typedef Stats statistics_type; ///< The statistics that will be gathered.
383  typedef typename thread_traits::assoc_colln_t colln_t; ///< The type of the internal associative collection.
384  typedef typename colln_t::size_type size_type; ///< The size type of the internal associative collection.
385  typedef const typename thread_traits::mapped_type mapped_type; ///< The range-data type of the key type.
386  typedef typename thread_traits::flusher_pool_t::exception_type exception_type; ///< The base class from which the exceptions that will be thrown by this class are derived.
388  typedef typename os_traits::lock_traits lock_traits;
389 
390  /// The "low water mark" of the cache.
391  /**
392  The flush policy will attempt to ensure that size()>=low_water_mark.
393  But this is not guaranteed, because there may be outstanding cache fills from another thread.
394  \see size()
395  */
397  /// The "high water mark" of the cache.
398  /**
399  The flush policy will attempt to ensure that size()<=high_water_mark.
400  But this is not guaranteed, because there may be outstanding cache fills from another thread.
401  \see size()
402  */
404 
405  /// Create an empty read-only cache.
406  /**
407  \param low_water This is the low_water_mark of the cache. i.e. using the flush policy the cache will attempt to ensure that size()>=low_water_mark.
408  \param high_water This is the high_water_mark of the cache. i.e. using the flush policy the cache will attempt to ensure that size()<=high_water_mark.
409  \param num_threads This is the number of threads in the thread pool used for creating range-data for satisfying data requests. This number is set to prevent any data source (e.g. a database) from becoming overwhelmed by requests.
410  \param f The factory that is used to create a range-data item for a particular key.
411  \see size()
412  */
413  explicit constexpr __stdcall ro(const size_type low_water, const size_type high_water, const typename thread_traits::populate_pool_t::pool_type::size_type num_threads, const factory_type &f)
414  : base<Factory>(f), low_water_mark(low_water), high_water_mark(high_water), serialize(), flusher_pool(), populate_pool(num_threads), internal_cache(), statistics_() {
415  }
416  /// Create a read-only cache and populate it with some values.
417  /**
418  Note that a (possibly) asynchronous flush() may occur after the population to ensure that the
419  max_size of the cache is maintained.
420  \param low_water This is the low_water_mark of the cache. i.e. using the flush policy the cache will attempt to ensure that size()>=low_water_mark.
421  \param high_water This is the high_water_mark of the cache. i.e. using the flush policy the cache will attempt to ensure that size()<=high_water_mark.
422  \param num_threads This is the number of threads in the thread pool used for creating range-data for satisfying data requests. This number is set to prevent any data source (e.g. a database) from becoming overwhelmed by requests.
423  \param f The factory that is used to create a range-data item for a particular key.
424  \param beg An iterator to the beginning of the source data.
425  \param end An iterator to the end of the source data.
426  \see size()
427  \see insert()
428  */
429  template<typename Iter> constexpr __stdcall
430  ro(const size_type low_water, const size_type high_water, const typename thread_traits::populate_pool_t::pool_type::size_type num_threads, const factory_type &f, const Iter &beg, const Iter &end)
431  : base<Factory>(f), low_water_mark(low_water), high_water_mark(high_water), serialize(), flusher_pool(), populate_pool(num_threads), internal_cache(beg, end) {
432  }
433  __stdcall ro(const ro &);
434  virtual __stdcall ~ro(void) noexcept(true);
435 
436  ro & __fastcall operator=(const ro &);
437 
438  /// Indicate if the cache is empty.
439  /**
440  Note that this does not take into account any outstanding cache fills nor flush()s.
441  \return "true" iff size()==0.
442  \see size()
443  \see flush()
444  */
445  bool __fastcall empty(void) const noexcept(true) override;
446  /// Indicate the number of data items in the cache.
447  /**
448  Note that this does not take into account any outstanding cache fills nor flush()s.
449  \see flush()
450  */
451  const size_type __fastcall size(void) const noexcept(true);
452  /// Synchronously remove all data within the cache.
453  /**
454  Note that this does not take into account any outstanding cache fills nor flush()s.
455  Thus after this function it is not guaranteed that empty()==true.
456  This function will reset any statistics upon clearing all contents from the cache.
457  \see flush()
458  \see empty()
459  */
460  void __fastcall clear(void) override;
461 
462  /// Synchronously insert a range of values into the cache.
463  /**
464  Note that a (possibly) asynchronous flush() may occur after the population to ensure that the
465  size()~<=high_water_mark of the cache is maintained.
466  \param beg An iterator to the beginning of the source data.
467  \param end An iterator to the end of the source data.
468  \see flush()
469  */
470  template<typename Iter> void __fastcall
471  insert(const Iter &beg, const Iter &end) {
472  internal_cache.insert(beg, end);
473  flusher_pool<<flush_nonjoinable()<<flush_cache(*this);
474  }
475  /// Lookup and return the range-data value associated with a key from the map.
476  /**
477  The creation of the range-data value may be asynchronous, as an implementation-defined optimization for multi-threaded caches.
478  A (possibly) asynchronous flush() is called to ensure that the high_water_mark is not exceeded before this function returns. Note that for asynchronous calls to flush(), they are only run once: overlapping calls to flush() will cause the later calls to be quashed, such that no buffering of flush()es occurs.
479  Note that it is guaranteed that empty()==false after this call.
480  \param key The key to look up in the map and return the range-data value.
481  \return A const-qualified copy of the range data value associated with the key. This ensures that if an asynchronous flush() of the data item occurs, this will not leave a dangling reference in the client.
482  \see flush()
483  \see empty()
484  */
485  const mapped_type __fastcall operator[](const key_type &key);
486  /// A synchronous call to flush the cache according to the flush policy.
487  /**
488  Note that if two or more items within the cache satisfy the victimization criterion, and removing at least one of them would
489  ensure that the max_size criterion is satisfied, it is implementation defined regarding which of those items woulsd be flushed.
490  */
491  void __fastcall flush(void) override;
492 
493  const statistics_type & __fastcall statistics(void) const noexcept(true);
494 
495  private:
496  typedef FP victimization_traits;
497  typedef typename lock_traits::critical_section_type atomic_t;
498  typedef typename atomic_t::write_unlockable_type lock_t;
499  typedef typename thread_traits::flusher_pool_t::nonjoinable flush_nonjoinable;
500 
501  mutable atomic_t serialize;
502  mutable typename thread_traits::flusher_pool_t flusher_pool;
503  mutable typename thread_traits::populate_pool_t populate_pool;
504  mutable colln_t internal_cache;
505  mutable statistics_type statistics_;
506  };
507 
508  /**
509  \todo Implement using the advice given in "Standard C++ IOStreams and Locales" by A.Langer & K.Kreft, page 170.
510  */
511  template<class F_,class P_,class TT_,class S_> tostream & __fastcall
512  operator<<(tostream &os, const ro<F_,P_,TT_,S_> &r) {
513  os<<_T("Low water-mark=")<<r.low_water_mark
514  <<_T(", high water-mark=")<<r.high_water_mark
515  <<_T(", current number of elements=")<<r.size();
516  return os;
517  }
518 
519 } } }
520 
521 #include "cache_impl.hpp"