block_alloc.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='BLOCK_ALLOC_H'>
00024 
00025  $Id: block_alloc.h,v 1.5 2010/06/23 23:42:57 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 BLOCK_ALLOC_H
00053 #define BLOCK_ALLOC_H
00054 
00055 /**\cond skip */
00056 #include "dynarray.h"
00057 #include "mem_block.h"
00058 
00059 // for placement new support, which users need
00060 #include <new>
00061 #include <w.h>
00062 #include <stdlib.h>
00063 #include <deque>
00064 
00065 /* Forward decls so we can do proper friend declarations later
00066  */
00067 template<class T, size_t MaxBytes>
00068 class block_alloc;
00069 
00070 template<class T, size_t MaxBytes>
00071 inline
00072 void* operator new(size_t nbytes, block_alloc<T, MaxBytes> &alloc);
00073 
00074 template<class T, size_t MaxBytes>
00075 inline
00076 void operator delete(void* ptr, block_alloc<T, MaxBytes> &alloc);
00077 
00078 // a basic block_pool backed by a dynarray
00079 struct dynpool : memory_block::block_pool {
00080     typedef memory_block::block mblock;
00081     pthread_mutex_t _lock;
00082     dynarray        _arr;
00083     std::deque<mblock*> _free_list;
00084     size_t      _chip_size;
00085     size_t      _chip_count;
00086     size_t      _log2_block_size;
00087     size_t      _arr_end;
00088 
00089     NORET       dynpool(size_t chip_size, size_t chip_count,
00090                 size_t log2_block_size, size_t max_bytes);
00091     
00092     virtual
00093     NORET       ~dynpool();
00094     
00095     virtual
00096     bool        validate_pointer(void* ptr);
00097 
00098 protected:
00099 
00100     size_t      _size() const;
00101 
00102     mblock*     _at(size_t i);
00103     
00104     virtual
00105     mblock*         _acquire_block();
00106 
00107     virtual
00108     void        _release_block(mblock* b);
00109     
00110 };
00111 
00112 
00113 /** \brief A factory for speedier allocation from the heap.
00114  *
00115  * This allocator is intended for use in a multithreaded environment
00116  * where many short-lived objects are created and released.
00117  *
00118  * Allocations are not thread safe, but deallocations are. This allows
00119  * each thread to allocate objects cheaply, without worrying about who
00120  * will eventually deallocate them (they must still be deallocated, of
00121  * course).
00122  * To use: give each thread its own allocator: that provides the thread-safety.
00123  *
00124  * The factory is backed by a global dynarray which manages
00125  * block-level allocation; each block provides N chips to hand out to
00126  * clients. The allocator maintains a cache of blocks just large
00127  * enough that it can expect to recycle the oldest cached block as
00128  * each active block is consumed; the cache can both grow and shrink
00129  * to match demand.
00130  *
00131  * PROS:
00132  * - most allocations are extremely cheap -- no malloc(), no atomic ops
00133  * - deallocations are also cheap -- one atomic op
00134  * - completely immune to the ABA problem 
00135  * - memory can be redistributed among threads between bursts
00136 
00137  * CONS:
00138  *
00139  * - each thread must have its own allocator, which means managing
00140  *   thread-local storage (if compilers ever support non-POD __thread
00141  *   objects, this problem would go away).
00142  *
00143  * - though threads attempt to keep their caches reasonably-sized,
00144  *   they will only do so at allocation or thread destruction time,
00145  *   leading to potential hoarding
00146  *
00147  * - memory leaks (or unexpectedly long-lived objects) are extremly
00148  *   expensive because they keep a whole block from being freed
00149  *   instead of just one object. However, the remaining chips of each
00150  *   block are at least available for reuse. 
00151  */
00152 
00153 template<class T, class Pool=dynpool, size_t MaxBytes=0>
00154 struct block_pool 
00155 {
00156     typedef memory_block::meta_block_size<sizeof(T)> BlockSize;
00157 
00158     // use functions because dbx can't see enums very well
00159     static size_t block_size() { return BlockSize::BYTES; }
00160     static size_t chip_count() { return BlockSize::COUNT; }
00161     static size_t chip_size()  { return sizeof(T); }
00162 
00163     // gets old typing this over and over...
00164 #define TEMPLATE_ARGS chip_size(), chip_count(), block_size()
00165 
00166     static Pool* get_pool() {
00167     static Pool p(chip_size(), chip_count(), BlockSize::LOG2,
00168               MaxBytes? MaxBytes : 1024*1024*1024);
00169     return &p;
00170     }
00171   
00172     block_pool()
00173     : _blist(get_pool(), TEMPLATE_ARGS)
00174     {
00175     }
00176 
00177     /* Acquire one object from the pool.
00178      */
00179     void* acquire() {
00180     return _blist.acquire(TEMPLATE_ARGS);
00181     }
00182     
00183     /* Verify that we own the object then find its block and report it
00184        as released. If \e destruct is \e true then call the object's
00185        desctructor also.
00186      */
00187     static
00188     void release(void* ptr) {
00189     assert(get_pool()->validate_pointer(ptr));
00190     memory_block::block::release(ptr, TEMPLATE_ARGS);
00191     }
00192     
00193 private:
00194     memory_block::block_list _blist;
00195 
00196 };
00197 
00198 template<class T, size_t MaxBytes=0>
00199 struct block_alloc {
00200     
00201     typedef block_pool<T, dynpool, MaxBytes> Pool;
00202 
00203     static
00204     void destroy_object(T* ptr) {
00205     ptr->~T();
00206     Pool::release(ptr); 
00207     }
00208     
00209 private:
00210     Pool _pool;
00211     
00212     // let operator new/delete access alloc()
00213     friend void* operator new<>(size_t, block_alloc<T,MaxBytes> &);
00214     friend void  operator delete<>(void*, block_alloc<T,MaxBytes> &);
00215 };
00216 
00217 template<class T, size_t MaxBytes>
00218 inline
00219 void* operator new(size_t nbytes, block_alloc<T,MaxBytes> &alloc) 
00220 {
00221     (void) nbytes; // keep gcc happy
00222     w_assert1(nbytes == sizeof(T));
00223     return alloc._pool.acquire();
00224 }
00225 
00226 /* No, this isn't a way to do "placement delete" (if only the language
00227    allowed that symmetry)... this operator is only called -- by the
00228    compiler -- if T's constructor throws
00229  */
00230 template<class T, size_t MaxBytes>
00231 inline
00232 void operator delete(void* ptr, block_alloc<T,MaxBytes> &alloc) 
00233 {
00234     block_alloc<T,MaxBytes>::Pool::release(ptr);
00235     w_assert2(0); // let a debug version catch this.
00236 }
00237 
00238 inline
00239 size_t dynpool::_size() const {
00240     return _arr_end >> _log2_block_size;
00241 }
00242 
00243 inline
00244 dynpool::mblock* dynpool::_at(size_t i) {
00245     size_t offset = i << _log2_block_size;
00246     union { char* c; mblock* b; } u = {_arr+offset};
00247     return u.b;
00248 }
00249 
00250 
00251 
00252 #undef TEMPLATE_ARGS
00253 
00254 // prototype for the object cache TFactory...
00255 template<class T>
00256 struct object_cache_default_factory {
00257     // these first three are required... the template args are optional
00258     static T*
00259     construct(void* ptr) { return new (ptr) T; }
00260 
00261     static void
00262     reset(T* t) { /* do nothing */ }
00263 
00264     static T*
00265     init(T* t) { /* do nothing */ return t; }
00266 };
00267 
00268 template<class T>
00269 struct object_cache_initializing_factory {
00270     // these first three are required... the template args are optional
00271     static T*
00272     construct(void* ptr) { return new (ptr) T; }
00273     
00274     static void
00275     reset(T* t) { t->reset(); }
00276 
00277     static T*
00278     init(T* t) { t->init(); return t; }
00279 
00280     // matched by object_cache::acquire below, but with the extra first T* arg...
00281     template<class Arg1>
00282     static T* init(T* t, Arg1 arg1) { t->init(arg1); return t; }
00283     template<class Arg1, class Arg2>
00284     static T* init(T* t, Arg1 arg1, Arg2 arg2) { t->init(arg1, arg2); return t; }    
00285     template<class Arg1, class Arg2, class Arg3>
00286     static T* init(T* t, Arg1 arg1, Arg2 arg2, Arg3 arg3) { t->init(arg1, arg2, arg3); return t; }
00287 };
00288 
00289 template <class T, class TFactory=object_cache_default_factory<T>, size_t MaxBytes=0>
00290 struct object_cache {
00291     
00292     // for convenience... make sure to extend the object_cache_default_factory to match!!!
00293     T* acquire() {
00294     return TFactory::init(_acquire());
00295     }
00296     template<class Arg1>
00297     T* acquire(Arg1 arg1) {
00298     return TFactory::init(_acquire(), arg1);
00299     }
00300     template<class Arg1, class Arg2>
00301     T* acquire(Arg1 arg1, Arg2 arg2) {
00302     return TFactory::init(_acquire(), arg1, arg2);
00303     }    
00304     template<class Arg1, class Arg2, class Arg3>
00305     T* acquire(Arg1 arg1, Arg2 arg2, Arg3 arg3) {
00306     return TFactory::init(_acquire(), arg1, arg2, arg3);
00307     }
00308     
00309     T* _acquire() {
00310     // constructed when its block was allocated...
00311     union { void* v; T* t; } u = {_pool.acquire()};
00312     return u.t;
00313     }
00314 
00315     static
00316     void release(T* obj) {
00317     TFactory::reset(obj);
00318     Pool::release(obj);
00319     }
00320 
00321 private:
00322     
00323     struct cache_pool : dynpool {
00324 
00325     // just a pass-thru...
00326     NORET cache_pool(size_t cs, size_t cc, size_t l2bs, size_t mb)
00327         : dynpool(cs, cc, l2bs, mb)
00328     {
00329     }
00330     
00331     virtual void _release_block(mblock* b);
00332     virtual mblock* _acquire_block();
00333     virtual NORET ~cache_pool();
00334     };
00335 
00336     typedef block_pool<T, cache_pool, MaxBytes> Pool;
00337     
00338     Pool _pool;
00339 
00340 };
00341 
00342 template <class T, class TF, size_t M>
00343 inline
00344 void object_cache<T,TF,M>::cache_pool::_release_block(mblock* b) {
00345     union { cache_pool* cp; memory_block::block_list* bl; } u={this};
00346     b->_owner = u.bl;
00347     dynpool::_release_block(b);
00348 }
00349     
00350 /* Intercept untagged (= newly-allocated) blocks in order to
00351    construct the objects they contain.
00352 */
00353 template <class T, class TF, size_t M>
00354 inline
00355 dynpool::mblock* object_cache<T,TF,M>::cache_pool::_acquire_block() {
00356     dynpool::mblock* b = dynpool::_acquire_block();
00357     void* me = this;
00358     if(me != b->_owner) {
00359     // new block -- initialize its objects
00360     for(size_t j=0; j < Pool::chip_count(); j++) 
00361         TF::construct(b->_get(j, Pool::chip_size()));
00362     b->_owner = 0;
00363     }
00364     return b;
00365 }
00366 
00367 /* Destruct all cached objects before going down
00368  */
00369 template <class T, class TF, size_t M>
00370 inline
00371 NORET object_cache<T,TF,M>::cache_pool::~cache_pool() {
00372     size_t size = _size();
00373     for(size_t i=0; i < size; i++) {
00374     mblock* b = _at(i);
00375     for(size_t j=0; j < Pool::chip_count(); j++) {
00376         union { char* c; T* t; } u = {b->_get(j, Pool::chip_size())};
00377         u.t->~T();
00378     }
00379     }
00380 }
00381 
00382 /**\endcond skip */
00383 #endif

Generated on Wed Jul 7 17:22:32 2010 for Shore Storage Manager by  doxygen 1.4.7