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.8 2010/09/23 13:52:36 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 : public 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_static_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_static_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         w_assert0(get_static_pool()->validate_pointer(ptr));
00190         memory_block::block::release_chip(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         if(ptr == NULL) return;
00206 
00207         ptr->~T();
00208         Pool::release(ptr);  // static method
00209         // that releases the ptr into a static pool
00210         // (member of block_pool) (of type dynpool)
00211     }
00212     
00213 private:
00214     Pool _pool;
00215     
00216     // let operator new/delete access alloc()
00217     friend void* operator new<>(size_t, block_alloc<T,MaxBytes> &);
00218     friend void  operator delete<>(void*, block_alloc<T,MaxBytes> &);
00219 };
00220 
00221 template<class T, size_t MaxBytes>
00222 inline
00223 void* operator new(size_t nbytes, block_alloc<T,MaxBytes> &alloc) 
00224 {
00225     (void) nbytes; // keep gcc happy
00226     w_assert1(nbytes == sizeof(T));
00227     return alloc._pool.acquire();
00228 }
00229 
00230 /* No, this isn't a way to do "placement delete" (if only the language
00231    allowed that symmetry)... this operator is only called -- by the
00232    compiler -- if T's constructor throws
00233  */
00234 template<class T, size_t MaxBytes>
00235 inline
00236 void operator delete(void* ptr, block_alloc<T,MaxBytes> & /*alloc*/) 
00237 {
00238     if(ptr == NULL) return;
00239     block_alloc<T,MaxBytes>::Pool::release(ptr);
00240     w_assert2(0); // let a debug version catch this.
00241 }
00242 
00243 inline
00244 size_t dynpool::_size() const {
00245     return _arr_end >> _log2_block_size;
00246 }
00247 
00248 inline
00249 dynpool::mblock* dynpool::_at(size_t i) {
00250     size_t offset = i << _log2_block_size;
00251     union { char* c; mblock* b; } u = {_arr+offset};
00252     return u.b;
00253 }
00254 
00255 
00256 
00257 #undef TEMPLATE_ARGS
00258 
00259 // prototype for the object cache TFactory...
00260 template<class T>
00261 struct object_cache_default_factory {
00262     // these first three are required... the template args are optional
00263     static T*
00264     construct(void* ptr) { return new (ptr) T; }
00265 
00266     static void
00267     reset(T* t) { /* do nothing */ }
00268 
00269     static T*
00270     init(T* t) { /* do nothing */ return t; }
00271 };
00272 
00273 template<class T>
00274 struct object_cache_initializing_factory {
00275     // these first three are required... the template args are optional
00276     static T*
00277     construct(void* ptr) { return new (ptr) T; }
00278     
00279     static void
00280     reset(T* t) { t->reset(); }
00281 
00282     static T*
00283     init(T* t) { t->init(); return t; }
00284 
00285     // matched by object_cache::acquire below, but with the extra first T* arg...
00286     template<class Arg1>
00287     static T* init(T* t, Arg1 arg1) { t->init(arg1); return t; }
00288     template<class Arg1, class Arg2>
00289     static T* init(T* t, Arg1 arg1, Arg2 arg2) { t->init(arg1, arg2); return t; }    
00290     template<class Arg1, class Arg2, class Arg3>
00291     static T* init(T* t, Arg1 arg1, Arg2 arg2, Arg3 arg3) { t->init(arg1, arg2, arg3); return t; }
00292 };
00293 
00294 template <class T, class TFactory=object_cache_default_factory<T>, size_t MaxBytes=0>
00295 struct object_cache {
00296     
00297     // for convenience... make sure to extend the object_cache_default_factory to match!!!
00298     T* acquire() {
00299         return TFactory::init(_acquire());
00300     }
00301     template<class Arg1>
00302     T* acquire(Arg1 arg1) {
00303         return TFactory::init(_acquire(), arg1);
00304     }
00305     template<class Arg1, class Arg2>
00306     T* acquire(Arg1 arg1, Arg2 arg2) {
00307         return TFactory::init(_acquire(), arg1, arg2);
00308     }    
00309     template<class Arg1, class Arg2, class Arg3>
00310     T* acquire(Arg1 arg1, Arg2 arg2, Arg3 arg3) {
00311         return TFactory::init(_acquire(), arg1, arg2, arg3);
00312     }
00313     
00314     T* _acquire() {
00315     // constructed when its block was allocated...
00316         union { void* v; T* t; } u = {_pool.acquire()};
00317         return u.t;
00318     }
00319 
00320     static
00321     void release(T* obj) {
00322         TFactory::reset(obj);
00323         Pool::release(obj);
00324     }
00325 
00326 private:
00327     
00328     struct cache_pool : public dynpool {
00329 
00330         // just a pass-thru...
00331         NORET cache_pool(size_t cs, size_t cc, size_t l2bs, size_t mb)
00332             : dynpool(cs, cc, l2bs, mb)
00333         {
00334         }
00335     
00336         virtual void _release_block(mblock* b);
00337         virtual mblock* _acquire_block();
00338         virtual NORET ~cache_pool();
00339     };
00340 
00341     typedef block_pool<T, cache_pool, MaxBytes> Pool;
00342     
00343     Pool _pool;
00344 
00345 };
00346 
00347 template <class T, class TF, size_t M>
00348 inline
00349 void object_cache<T,TF,M>::cache_pool::_release_block(mblock* b) {
00350     union { cache_pool* cp; memory_block::block_list* bl; } u={this};
00351     b->_owner = u.bl;
00352     dynpool::_release_block(b);
00353 }
00354     
00355 /* Intercept untagged (= newly-allocated) blocks in order to
00356    construct the objects they contain.
00357 */
00358 template <class T, class TF, size_t M>
00359 inline
00360 dynpool::mblock* object_cache<T,TF,M>::cache_pool::_acquire_block() {
00361     dynpool::mblock* b = dynpool::_acquire_block();
00362     void* me = this;
00363     if(me != b->_owner) {
00364         // new block -- initialize its objects
00365         for(size_t j=0; j < Pool::chip_count(); j++) 
00366             TF::construct(b->_get(j, Pool::chip_size()));
00367         b->_owner = 0;
00368     }
00369     return b;
00370 }
00371 
00372 /* Destruct all cached objects before going down
00373  */
00374 template <class T, class TF, size_t M>
00375 inline
00376 NORET object_cache<T,TF,M>::cache_pool::~cache_pool() {
00377     size_t size = _size();
00378     for(size_t i=0; i < size; i++) {
00379         mblock* b = _at(i);
00380         for(size_t j=0; j < Pool::chip_count(); j++) {
00381             union { char* c; T* t; } u = {b->_get(j, Pool::chip_size())};
00382             u.t->~T();
00383         }
00384     }
00385 }
00386 
00387 /**\endcond skip */
00388 #endif

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