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.4 2010/12/08 17:37:37 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 
00060 #define NORET
00061 
00062 /* NOTE: The classes defined here are non-template helpers for the
00063    template-based block_pool class. Because they are only intended to
00064    be used from template code, they accept "template" parameters with
00065    every call instead of storing them internally: register-passing
00066    from the template code is less expensive than loading the value
00067    from memory, and also minimizes per-block space overhead.
00068  */
00069 
00070 namespace memory_block {
00071 #if 0 /*keep emacs happy */
00072 } // keep emacs happy...
00073 #endif
00074 
00075 /* GCC can't handle zero length arrays, but has an extension to handle
00076    unsized ones at the end of a struct. Meanwhile, SunStudio can't
00077    handle unsized arrays, but has an extension to allow zero-length
00078    arrays.
00079  */
00080 #ifdef __GNUC__
00081 #define EMPTY_ARRAY_DIM
00082 #else
00083 #define EMPTY_ARRAY_DIM 0
00084 #endif
00085 
00086 // forward decl...
00087 struct block_list;
00088 
00089 /* A bitmap allocator.
00090 
00091    This class tracks the bookkeeping of chip allocation while leaving
00092    the corresponding memory management to someone else. The
00093    implementation requires that chip allocation be single-threaded
00094    (presumably by some owner thread), but is thread-safe with respect
00095    to deallocation.
00096 
00097    A given "chip" may be in one of three states:
00098    
00099        USABLE: available to be allocated
00100     
00101        IN-USE: allocated but not yet freed
00102     
00103        ZOMBIE: freed more recently than the last recycling pass
00104 
00105    Allocation is double-buffered in a sense: at the beginning of each
00106    allocation round, the owning thread unions the current set of
00107    zombie chips into the usable set; in-use chips are ignored.
00108 
00109    The class has two members to support higher-level functionality:
00110    
00111        _owner: an opaque pointer which is used to verify that blocks
00112            are being released properly
00113         
00114        _next: an embedded linked list node for use by the owner and
00115            otherwise ignored by the implementation
00116  */
00117 struct block_bits {
00118     
00119     typedef w_base_t::uint8_t     bitmap; 
00120 
00121     enum         { MAX_CHIPS=8*sizeof(bitmap) };
00122     
00123     NORET        block_bits(size_t chip_count);
00124     
00125     size_t       acquire_contiguous(size_t chip_count);
00126 
00127     void         release_contiguous(size_t idx, size_t chip_count);
00128     
00129     static
00130     bitmap       create_mask(size_t bits_set);
00131 
00132     bitmap volatile*     usable_chips() { return &_usable_chips; }
00133     
00134     size_t        usable_count() const { return _popc(_usable_chips); }
00135     size_t        zombie_count() const { return _popc(_zombie_chips); }
00136 
00137     void          recycle();
00138     
00139     static
00140     size_t        _popc(bitmap bm);
00141 
00142     bitmap        _usable_chips; // available as of last recycling (1thr)
00143     bitmap volatile    _zombie_chips; // deallocations since last recycling (racy)
00144 };
00145 
00146 /* Control structure for one block of allocatable memory.
00147 
00148    The memory is assumed to be /block_size/ bytes long and must be
00149    aligned on a /block_size/-sized boundary. The implementation
00150    exploits the block's alignment to compute the block control for
00151    pointers being released, meaning there is no per-chip space
00152    overhead.
00153 
00154    One caveat of the implicit control block pointer approach is that
00155    the caller is responsible to ensure that any chip passed to
00156    /release()/ is actually part of a block (presumably by verifying
00157    that the address range is inside an appropriate memory pool).
00158 
00159    This class manages memory only very loosely: distributing and
00160    accepting the return of /chip_size/-sized chips. At no time does it
00161    access any of the memory it manages.
00162  */
00163 struct block {
00164     NORET        block(size_t chip_size, size_t chip_count, size_t block_size);
00165     void*        acquire_chip(size_t chip_size, size_t chip_count, size_t block_size);
00166 
00167     // WARNING: the caller must ensure that ptr is in a valid memory range
00168     static
00169     void         release_chip(void* ptr, size_t chip_size, size_t chip_count, size_t block_size);
00170 
00171     void         recycle() { _bits.recycle(); }
00172 
00173     char*        _get(size_t idx, size_t chip_size);
00174     
00175     block_bits      _bits;
00176     block_list*     _owner;
00177     block*          _next;
00178     // The memory managed:
00179     char            _data[EMPTY_ARRAY_DIM];
00180 };
00181 
00182 
00183 struct block_pool {
00184     block*        acquire_block(block_list* owner);
00185     block*        release_block(block* b);
00186 
00187     // true if /ptr/ points to data inside some block managed by this pool
00188     virtual bool    validate_pointer(void* ptr)=0;
00189     
00190     virtual NORET    ~block_pool() { }
00191     
00192 protected:
00193     
00194     virtual block*   _acquire_block()=0;
00195     // takes back b, then returns b->_next
00196     virtual void     _release_block(block* b)=0;
00197 };
00198 
00199 struct block_list {
00200     NORET        block_list(block_pool* pool, size_t chip_size,
00201                    size_t chip_count, size_t block_size);
00202     
00203     NORET        ~block_list();
00204     
00205     void*         acquire(size_t chip_size, size_t chip_count, size_t block_size);
00206     block*        acquire_block(size_t block_size);
00207 
00208     void*        _slow_acquire(size_t chip_size, size_t chip_count, size_t block_size);
00209     void         _change_blocks(size_t chip_size, size_t chip_count, size_t block_size);
00210 
00211     block         _fake_block;
00212     block*        _tail;
00213     block_pool*   _pool;
00214     size_t        _hit_count;
00215     double        _avg_hit_rate;
00216     
00217 };
00218 
00219 
00220 inline
00221 block* block_pool::acquire_block(block_list* owner) {
00222     block* b = _acquire_block();
00223     b->_owner = owner;
00224     b->_next = 0;
00225     b->_bits.recycle();
00226     return b;
00227 }
00228 
00229 inline
00230 block* block_pool::release_block(block* b) {
00231     assert(validate_pointer(b));
00232     block* next = b->_next;
00233     b->_next = 0;
00234     b->_owner = 0;
00235     _release_block(b);
00236     return next;
00237 }
00238 
00239 
00240 /* A compile-time predicate.
00241 
00242    Compilation will fail for B=false because only B=true has a definition
00243 */
00244 template<bool B>
00245 struct fail_unless;
00246 
00247 template<>
00248 struct fail_unless<true> {
00249     static bool valid() { return true; }
00250 };
00251 
00252 
00253 
00254 /* A compile-time bounds checker.
00255 
00256    Fails to compile unless /L <= N <= U/
00257  */
00258 template<int N, int L, int U>
00259 struct bounds_check : public fail_unless<(L <= N) && (N <= U)> {
00260     static bool valid() { return true; }
00261 };
00262 
00263 
00264 
00265 /* A template class which, given some positive compile-time constant
00266    integer /x/, computes the compile-time constant value of
00267    /floor(log2(x))/
00268  */
00269 
00270 template <int N>
00271 struct meta_log2 : public fail_unless<(N > 2)> {
00272     enum { VALUE=1+meta_log2<N/2>::VALUE };
00273 };
00274 
00275 template<>
00276 struct meta_log2<2> {
00277     enum { VALUE=1 };
00278 };
00279 
00280 template<>
00281 struct meta_log2<1> {
00282     enum { VALUE=0 };
00283 };
00284 
00285 template<int A, int B>
00286 struct meta_min {
00287     enum { VALUE = (A < B)? A : B };
00288 };
00289 
00290 /* A template class which computes the optimal block size. Too large
00291    a block and the bitmap can't reach all the objects that fit,
00292    wasting space. Too small and the bitmap is mostly empty, leading to
00293    high overheads (in both space and time).
00294 
00295    For any given parameters there exists only one value of /BYTES/
00296    which is a power of two and utilizes 50% < util <= 100% of a
00297    block_bit's chips. 
00298  */
00299 template<int ChipSize, int OverheadBytes=sizeof(memory_block::block), int MaxChips=block_bits::MAX_CHIPS>
00300 struct meta_block_size : public fail_unless<(ChipSize > 0 && OverheadBytes >= 0)> {
00301     enum { CHIP_SIZE    = ChipSize };
00302     enum { OVERHEAD     = OverheadBytes };
00303     enum { MAX_CHIPS     = MaxChips };
00304     enum { MIN_CHIPS     = MAX_CHIPS/2 + 1 };
00305     enum { BYTES_NEEDED = MIN_CHIPS*ChipSize+OverheadBytes };
00306     enum { LOG2     = meta_log2<2*BYTES_NEEDED-1>::VALUE };
00307     
00308     enum { BYTES     = 1 << LOG2 };
00309     fail_unless<((BYTES &- BYTES) == BYTES)>     power_of_two;
00310     
00311     /* ugly corner case...
00312 
00313        if chips are small compared to overhead then we can end up with
00314        space for more than MAX_CHIPS. However, cutting the block size
00315        in half wouldn't leave enough chips behind so we're stuck.
00316 
00317        Note that this wasted space would be small compared with the
00318        enormous overhead that causes the situation in the first place.
00319      */    
00320     enum { REAL_COUNT     = (BYTES-OverheadBytes)/ChipSize };
00321     fail_unless<((OVERHEAD + MIN_CHIPS*ChipSize) > (int)BYTES/2)> sane_chip_count;
00322     
00323     enum { COUNT     = meta_min<MAX_CHIPS, REAL_COUNT>::VALUE };
00324     bounds_check<COUNT, MIN_CHIPS, MAX_CHIPS>     bounds;
00325 
00326 };
00327 
00328 } // namespace memory_block
00329 
00330 /**\endcond skip */
00331 
00332 #endif

Generated on Thu Dec 9 08:42:27 2010 for Shore Storage Manager by  doxygen 1.4.7