mem_block.h

00001 /* -*- mode:C++; c-basic-offset:4 -*-
00002      Shore-MT -- Multi-threaded port of the SHORE storage manager
00003    
00004                        Copyright (c) 2007-2009
00005       Data Intensive Applications and Systems Labaratory (DIAS)
00006                Ecole Polytechnique Federale de Lausanne
00007    
00008                          All Rights Reserved.
00009    
00010    Permission to use, copy, modify and distribute this software and
00011    its documentation is hereby granted, provided that both the
00012    copyright notice and this permission notice appear in all copies of
00013    the software, derivative works or modified versions, and any
00014    portions thereof, and that both notices appear in supporting
00015    documentation.
00016    
00017    This code is distributed in the hope that it will be useful, but
00018    WITHOUT ANY WARRANTY; without even the implied warranty of
00019    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. THE AUTHORS
00020    DISCLAIM ANY LIABILITY OF ANY KIND FOR ANY DAMAGES WHATSOEVER
00021    RESULTING FROM THE USE OF THIS SOFTWARE.
00022 */
00023 /*<std-header orig-src='shore' incl-file-exclusion='MEM_BLOCK_H'>
00024 
00025  $Id: mem_block.h,v 1.6 2012/01/02 21:52:21 nhall Exp $
00026 
00027 SHORE -- Scalable Heterogeneous Object REpository
00028 
00029 Copyright (c) 1994-99 Computer Sciences Department, University of
00030                       Wisconsin -- Madison
00031 All Rights Reserved.
00032 
00033 Permission to use, copy, modify and distribute this software and its
00034 documentation is hereby granted, provided that both the copyright
00035 notice and this permission notice appear in all copies of the
00036 software, derivative works or modified versions, and any portions
00037 thereof, and that both notices appear in supporting documentation.
00038 
00039 THE AUTHORS AND THE COMPUTER SCIENCES DEPARTMENT OF THE UNIVERSITY
00040 OF WISCONSIN - MADISON ALLOW FREE USE OF THIS SOFTWARE IN ITS
00041 "AS IS" CONDITION, AND THEY DISCLAIM ANY LIABILITY OF ANY KIND
00042 FOR ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
00043 
00044 This software was developed with support by the Advanced Research
00045 Project Agency, ARPA order number 018 (formerly 8230), monitored by
00046 the U.S. Army Research Laboratory under contract DAAB07-91-C-Q518.
00047 Further funding for this work was provided by DARPA through
00048 Rome Research Laboratory Contract No. F30602-97-2-0247.
00049 
00050 */
00051 
00052 #ifndef __MEM_BLOCK_H
00053 #define __MEM_BLOCK_H
00054 
00055 /**\cond skip */
00056 #include <stddef.h>
00057 #include <cassert>
00058 #include "w_base.h"
00059 /**\endcond skip */
00060 
00061 #define NORET
00062 
00063 /**\brief Per-thread heap memory management.
00064  * \ingroup HEAPMGMT
00065  */
00066 /*
00067  * Several classes defined here are non-template helpers for the
00068  * template-based block_pool<> class. Because they are only intended to
00069  * be used from template code, they accept "template" parameters with
00070  * every call instead of storing them internally: register-passing
00071  * from the template code is less expensive than loading the value
00072  * from memory, and also minimizes per-block space overhead.
00073  */
00074 namespace memory_block {
00075 #if 0 /*keep emacs happy */
00076 } // keep emacs happy...
00077 #endif
00078 
00079 // defined so we can add debugging
00080 typedef union { 
00081     void* v; 
00082     size_t n; 
00083     char* c; 
00084 } chip_t;
00085 
00086 /* GCC can't handle zero length arrays, but has an extension to handle
00087    unsized ones at the end of a struct. Meanwhile, SunStudio can't
00088    handle unsized arrays, but has an extension to allow zero-length
00089    arrays.
00090  */
00091 #ifdef __GNUC__
00092 #define EMPTY_ARRAY_DIM
00093 #else
00094 #define EMPTY_ARRAY_DIM 0
00095 #endif
00096 
00097 // forward decl...
00098 struct block_list;
00099 
00100 /**\brief A helper class for the chip-allocator class, block.
00101  *
00102  * This class tracks the bookkeeping of chip allocation while leaving
00103  * the corresponding memory management to someone else. 
00104  * The implementation requires that chip allocation be single-threaded
00105  * (presumably by some owner thread), but is thread-safe with respect
00106  * to deallocation.
00107  *
00108  * A given "chip" may be in one of three states:
00109  * -     USABLE: available to be allocated
00110  * -     IN-USE: allocated 
00111  * -     ZOMBIE: freed more recently than the last recycling pass
00112  *       and so are not allocated but not usable.
00113  *
00114  * Allocation is double-buffered in a sense: at the beginning of each
00115  * allocation round, the owning thread unions the current set of
00116  * zombie chips into the usable set; in-use chips are ignored.
00117  *
00118  * The class has two members to support higher-level functionality:
00119  * 
00120  *     _owner: an opaque pointer which is used to verify that blocks
00121  *         are being released properly
00122  *         It is set by the block_list but never inspected. It is
00123  *         used by derived classes.
00124  *      
00125  *     _next: an embedded linked list node for use by the owner and
00126  *         otherwise ignored by the implementation
00127  */
00128 class block_bits {
00129     
00130 #ifdef BLOCK_LIST_UNIT_TEST
00131 public:
00132 #endif
00133     typedef w_base_t::uint8_t     bitmap; 
00134 public:
00135     enum         { MAX_CHIPS=8*sizeof(bitmap) };
00136 
00137     /**\brief Construct bitmaps for \em chip_count chips.
00138      * Used by block_of_chips.
00139      */
00140     NORET        block_bits(size_t chip_count);
00141 
00142     /**\brief acquire \em chip_count contiguous chips 
00143      * by finding adjacent bits in _usable_chips
00144      */
00145     size_t       acquire_contiguous(size_t chip_count);
00146     /**\brief release \em chip_count contiguous chips 
00147      * by setting the adjacent bits in _zombie_chips
00148      */
00149     void         release_contiguous(size_t idx, size_t chip_count);
00150     
00151 private:
00152     static
00153     bitmap       create_mask(size_t bits_set);
00154     const bitmap volatile* const   usable_chips() { return &_usable_chips; }
00155     
00156 public:
00157  /**\brief Return number of released-but-as-yet-unusable blocks. */
00158     size_t        zombie_count() const { return _popc(_zombie_chips); } // for unit tests
00159  /**\brief Return number of usable blocks. */
00160     size_t        usable_count() const { return _popc(_usable_chips); }
00161     /**\brief Make the zombied (released but not yet reusable) chips available.  */
00162     void          recycle();
00163     /**\brief Make the block advertise that it has nothing to give.
00164      * This is an optimization; used in block_list::block_list (q.v.).
00165      */
00166     void          fake_full() { _usable_chips = 0; _zombie_chips=0; }
00167 
00168 private:
00169     inline static
00170     size_t        _popc(bitmap bm) { return w_base_t::pop_count(bm); }
00171 
00172     bitmap        _usable_chips; // available as of last recycling (1thr)
00173     bitmap volatile    _zombie_chips; // deallocations since last recycling (racy)
00174 };
00175 
00176 /** \brief Control structure for one block of allocatable memory.
00177   * This control block is imposed on a chunk of memory allocated
00178   * by some other allocator, and this manages some number of fixed-sized
00179   * chips that fit into this block.
00180   *
00181   * The implementation exploits the block's alignment to 
00182   * compute the block control for pointers being released, 
00183   * meaning there is no per-chip space overhead other than the per-block
00184   * bitmap indicating which chips are available.
00185   *
00186   * One caveat of the this approach is that
00187   * the caller is responsible to ensure that any chip passed to
00188   * \em release() is actually part of a block 
00189   * (presumably by verifying
00190   * that the address range is inside an appropriate memory pool).
00191   * In practice this is done by calling dynpool::validate_ptr(chip-ptr).
00192   *
00193   * This class manages memory only very loosely: distributing and
00194   * accepting the return of \em chip_size -sized chips. At no time does it
00195   * access any of the memory it manages.
00196  */
00197 class block_of_chips {
00198 public:
00199     /**\brief Constructor.
00200       * The memory is assumed to be \em block_size  bytes long and must be
00201       * aligned on a \em block_size -sized boundary. 
00202       * The constructor checks the arithmetic to make sure
00203       * \em chip_count chips of size \em chip_size fit into the block.
00204       * The block of chips is the array _data.
00205      */
00206     NORET        block_of_chips(size_t chip_size, 
00207                          size_t chip_count, size_t block_size);
00208 
00209     /**\brief Constructor.
00210   * Acquire one chip. The chip_size, chip_count (chips/block) 
00211   * and block_size are template arguments and do not change. 
00212   */
00213     chip_t       acquire_chip(size_t chip_size, size_t chip_count, 
00214                          size_t block_size);
00215 
00216     /**\brief This is static because it uses pointer arithmetic on the ptr
00217      * to find out to which block of chips this chip belongs; then it
00218      * releases the chip through that block.
00219      * The assumption is that the block cannot have been released to the
00220      * underlying allocator because the chip is still in use.
00221      *\warning the caller must ensure that ptr is in a valid memory range
00222      */
00223     static
00224     void         release_chip(chip_t ptr, size_t chip_size, size_t chip_count, 
00225                          size_t block_size);
00226     /**\brief Make zombies usable. */
00227     void         recycle() { _bits.recycle(); }
00228 
00229     /**\brief Return address of a single chip at given index. */
00230     chip_t       get(size_t idx, size_t chip_size) {  chip_t chip; chip.c = _data + idx*chip_size;  return chip;}
00231 
00232  // Yes, public.
00233     block_bits      _bits; // bitmaps
00234     block_list*     _owner; // list of which we are a member
00235     // set upon acquire, nulled-out upon release.
00236     block_of_chips* _next; // in the list that is _owner.
00237 
00238 private:
00239     // The memory managed:
00240     char            _data[EMPTY_ARRAY_DIM];
00241 };
00242 
00243 
00244 /**\brief Abstract base class for the classes that do
00245  * the real allocation of blocks of chips (e.g. dynpool).
00246  *
00247  * (This is the base class for dynpool. It is located here because
00248  * this allocator knows something of the structure of the blocks of chips
00249  * and because block_list uses this interface.)
00250  */
00251 class pool_of_blocks {
00252 public:
00253     /**\brief Acquire a block of chips and insert in the given owner list */
00254     block_of_chips*   acquire_block(block_list* owner);
00255     /**\brief Release an unused block of chips and return its "next" */
00256     block_of_chips*   release_block(block_of_chips* b);
00257 
00258     // true if /ptr/ points to data inside some block managed by this pool
00259     virtual bool      validate_pointer(void* ptr)=0;
00260     virtual NORET    ~pool_of_blocks() { }
00261     virtual size_t    capacity() const=0 ;
00262     virtual void      dump() const {}
00263     virtual size_t    freelist_size() const =0;
00264 protected:
00265     // The real work is done here, in the derived class(es):
00266     virtual block_of_chips* _acquire_block()=0;
00267     // take back b, then returns b->_next
00268     virtual void      _release_block(block_of_chips* b)=0;
00269 };
00270 
00271 inline
00272 block_of_chips* pool_of_blocks::acquire_block(block_list* owner) {
00273     block_of_chips* b = _acquire_block();
00274     b->_owner = owner;
00275     b->_next = 0;
00276     // just in case it's got some zombie chips. 
00277     b->_bits.recycle();
00278     return b;
00279 }
00280 
00281 inline
00282 block_of_chips* pool_of_blocks::release_block(block_of_chips* b) {
00283     assert(validate_pointer(b));
00284     block_of_chips* next = b->_next;
00285     b->_next = 0;
00286     b->_owner = 0;
00287     _release_block(b);
00288     return next;
00289 }
00290 
00291 
00292 /**\brief A helper class for block_pool<T...>, (a heap of T objects), and which
00293  * contains one of these lists.
00294  * 
00295  * This list class allocates (typeless) blocks of chips from its pool_of_blocks.
00296  * It may also release blocks to that pool_of_blocks if it finds
00297  * that the blocks aren't needed.  Slow acquire checks for the
00298  * ability to release before it delivers one.
00299  *
00300  * NOTE: 
00301  * chip_size, chip_count and block_size are fixed for any given list. 
00302  * Acquire, acquire_block and release occur one entity (chip or block) 
00303  * at a time.
00304  */
00305 class block_list {
00306 public:
00307 
00308     NORET      block_list(pool_of_blocks* pool, size_t chip_size,
00309                    size_t chip_count, size_t block_size);
00310     NORET      ~block_list();
00311 
00312     chip_t     acquire(size_t chip_size, size_t chip_count, size_t block_size);
00313 
00314     void       recycle(size_t chip_size, size_t chip_count, size_t block_size);
00315 
00316     void dump() const {
00317         block_of_chips *bc = _tail;
00318         int i=0;
00319         int usable=0;
00320         int zombie=0;
00321         if(bc && (bc != &_fake_block)) do {
00322             i++;
00323             usable += bc->_bits.usable_count();
00324             zombie += bc->_bits.zombie_count();
00325             bc = bc->_next; 
00326         } while (bc != NULL && bc != _tail);
00327 
00328         _pool->dump();
00329     }
00330 
00331     size_t pool_freelist_size() const { return _pool->freelist_size(); }
00332 private:
00333     block_of_chips* acquire_block(size_t block_size);
00334 
00335     chip_t        _slow_acquire(size_t chip_size, size_t chip_count, size_t block_size);
00336 #ifdef BLOCK_LIST_UNIT_TEST
00337 public:
00338 #endif
00339     void         _change_blocks(size_t chip_size, size_t chip_count, size_t block_size);
00340 
00341     block_of_chips   _fake_block;
00342     block_of_chips*  _tail;
00343     pool_of_blocks*   _pool;  // who allocates blocks for this list.
00344     size_t        _hit_count;
00345     double        _avg_hit_rate;
00346 };
00347 
00348 /** \brief Compile-time helper for several classes. 
00349  * Compilation will fail for B=false because only B=true has a definition
00350 */
00351 template<bool B>
00352 struct fail_unless;
00353 
00354 /** \brief Compile-time helper for several classes. 
00355  * Compilation will fail for B=false because only B=true has a definition
00356 */
00357 template<>
00358 struct fail_unless<true> {
00359     static bool valid() { return true; }
00360 };
00361 
00362 
00363 /** \brief Compile-time helper bounds-checker.
00364  * Fails to compile unless /L <= N <= U/
00365 */
00366 template<int N, int L, int U>
00367 struct bounds_check : public fail_unless<(L <= N) && (N <= U)> {
00368     static bool valid() { return true; }
00369 };
00370 
00371 
00372 /** \brief Compile-time helper to compute constant value \em floor(log2(x))
00373  */
00374 template <int N>
00375 struct meta_log2 : public fail_unless<(N > 2)> {
00376     enum { VALUE=1+meta_log2<N/2>::VALUE };
00377 };
00378 
00379 /** \brief instantiated meta_log2 */
00380 template<>
00381 struct meta_log2<2> {
00382     enum { VALUE=1 };
00383 };
00384 
00385 /** \brief instantiated meta_log2 */
00386 template<>
00387 struct meta_log2<1> {
00388     enum { VALUE=0 };
00389 };
00390 
00391 /** \brief Compile-time helper to compute constant value \em min(a,b)
00392  */
00393 template<int A, int B>
00394 struct meta_min {
00395     enum { VALUE = (A < B)? A : B };
00396 };
00397 
00398 /* A template class helper for block_pool<>.
00399  * This computes the optimal block size. 
00400  * Too large a block and the bitmap can't keep track of all the chips.
00401  * Too small and the bitmap is mostly empty, leading to high overheads 
00402  * (in both space and time).
00403  *
00404  * block_bits::MAX_CHIPS is determined by the bitmap size (now 64 bits)
00405  *
00406  * For any given parameters there exists only one value of /BYTES/
00407  * which is a power of two and utilizes 50% < util <= 100% of a
00408  * block_bit's chips. 
00409  */
00410 template<int ChipSize, 
00411     int OverheadBytes=sizeof(memory_block::block_of_chips), 
00412     int MaxChips=block_bits::MAX_CHIPS >
00413 class meta_block_size: public fail_unless<(ChipSize > 0 && OverheadBytes >= 0)> 
00414 {
00415     enum { CHIP_SIZE    = ChipSize };
00416     enum { OVERHEAD     = OverheadBytes };
00417     enum { MAX_CHIPS     = MaxChips };
00418     enum { MIN_CHIPS     = MAX_CHIPS/2 + 1 };
00419     enum { BYTES_NEEDED = MIN_CHIPS*ChipSize+OverheadBytes };
00420 public:
00421     /**\brief LOG2 exported for use by block_pool<> */
00422     enum { LOG2     = meta_log2<2*BYTES_NEEDED-1>::VALUE };
00423     
00424     /**\brief BYTES exported for use by block_pool<> */
00425     enum { BYTES     = 1 << LOG2 };
00426 private:
00427     fail_unless<((BYTES &- BYTES) == BYTES)>     power_of_two;
00428     
00429     /* ugly corner case...
00430 
00431        if chips are small compared to overhead then we can end up with
00432        space for more than MAX_CHIPS. However, cutting the block size
00433        in half wouldn't leave enough chips behind so we're stuck.
00434 
00435        Note that this wasted space would be small compared with the
00436        enormous overhead that causes the situation in the first place.
00437      */    
00438     enum { REAL_COUNT     = (BYTES-OverheadBytes)/ChipSize };
00439     fail_unless<((OVERHEAD + MIN_CHIPS*ChipSize) > (int)BYTES/2)> 
00440         sane_chip_count;
00441     
00442 public:
00443     /**\brief COUNT exported for use by block_pool<> */
00444     enum { COUNT     = meta_min<MAX_CHIPS, REAL_COUNT>::VALUE };
00445 private:
00446     bounds_check<COUNT, MIN_CHIPS, MAX_CHIPS>     bounds;
00447 
00448 }; // meta_block_size
00449 
00450 } // namespace memory_block
00451 
00452 /**\endcond skip */
00453 
00454 #endif

Generated on Mon Jan 2 15:13:56 2012 for Shore Storage Manager by  doxygen 1.4.7