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.10 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 BLOCK_ALLOC_H
00053 #define BLOCK_ALLOC_H
00054 
00055 #include "dynarray.h"
00056 #include "mem_block.h"
00057 
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 #if defined(SOLARIS2)
00066 // for typeid on Solaris:
00067 #include <typeinfo.h>
00068 #endif
00069 
00070 /* Forward decls so we can do proper friend declarations later
00071  */
00072 template<class T, size_t MaxBytes>
00073 class block_alloc;
00074 
00075 template<class T, size_t MaxBytes>
00076 inline
00077 void* operator new(size_t nbytes, block_alloc<T, MaxBytes> &alloc);
00078 
00079 template<class T, size_t MaxBytes>
00080 inline
00081 void operator delete(void* ptr, block_alloc<T, MaxBytes> &alloc);
00082 
00083 /**\brief Allocator of typeless blocks. 
00084  *
00085  * Derives from abstract pool_of_blocks. 
00086  * Allocates blocks, using mutexes for thread-safety,
00087  * from an underlying contiguous chunk of memory.
00088  * The underlying chunk can be extended but it remains contiguous.
00089  *
00090  * The underlying chunk is managed by the class dynarray, which
00091  * uses mmap.  Each dynpool has its own dynarray, so it gets 
00092  * its own mmapped chunk.
00093  *
00094  * When blocks are released back to the dynpool, they are stuffed into
00095  * a freelist but they are never given back to the dynarray in any
00096  * sense.
00097  *
00098  * Each heap of typed objects shares a static(sic) dynpool for that
00099  * type (well, for that block_pool<T> specialization, of which there
00100  * *could be* duplicates for a given T, depending on the template structure
00101  * of the client code); 
00102  *
00103  */
00104 class dynpool : public memory_block::pool_of_blocks {
00105 public:
00106     typedef memory_block::block_of_chips mblock;
00107 private:
00108     pthread_mutex_t    _lock;
00109     dynarray           _arr;
00110     std::deque<mblock*> _free_list;
00111     size_t        _chip_size;
00112     size_t        _chip_count;
00113     size_t        _log2_block_size;
00114     size_t        _arr_end;
00115 
00116 public:
00117     NORET         dynpool(size_t chip_size, size_t chip_count,
00118                         size_t log2_block_size, size_t max_bytes);
00119 
00120     // inherits    acquire_block(block_list* owner) from pool_of_blocks
00121     // inherits    release_block(block_of_chips* b) from pool_of_blocks
00122     
00123     virtual
00124     NORET        ~dynpool();
00125     
00126     // not provided by abstract base class:
00127  /**\brief Check that ptr falls in the range of the single underlying dynarray (mmapped range).  Print a message to stderr and return false if not. 
00128   * Return true if OK.
00129   */
00130     virtual
00131     bool         validate_pointer(void* ptr);
00132  /**\brief Return the max size of the underlying contiguous dynarray.
00133   * This is not the current size but only the maximum size to which it could possibly grow.
00134   */
00135     size_t       capacity() const { return _arr.capacity(); }
00136  /**\brief Return the number of items (blocks of chips) on the freelist.
00137   */
00138     size_t       freelist_size() const { return _free_list.size(); }
00139 
00140     virtual void      dump() const;
00141 
00142 protected:
00143     size_t       _size() const {
00144         return _arr_end >> _log2_block_size;
00145     }
00146     mblock*      _at(size_t i) {
00147         size_t offset = i << _log2_block_size;
00148         union { char* c; mblock* b; } u = {_arr+offset};
00149         return u.b;
00150     }
00151     
00152     virtual
00153     mblock*      _acquire_block(); 
00154 
00155     virtual
00156     void         _release_block(mblock* b);
00157     
00158 };
00159 
00160 
00161 /** \brief A factory for speedier allocation from the heap.
00162  *
00163  * This allocator is intended for use in a multithreaded environment
00164  * where many short-lived objects are created and released.
00165  *
00166  * Allocations are not thread safe, but deallocations are. This allows
00167  * each thread to allocate objects cheaply, without worrying about who
00168  * will eventually deallocate them (they must still be deallocated, of
00169  * course).
00170  * To use: give each thread its own allocator: that provides the thread-safety.
00171  *
00172  * The factory is backed by a global dynarray which manages
00173  * block-level allocation; each block provides N chips to hand out to
00174  * clients. The allocator maintains a cache of blocks just large
00175  * enough that it can expect to recycle the oldest cached block as
00176  * each active block is consumed; the cache can both grow and shrink
00177  * to match demand.
00178  *
00179  * PROS:
00180  * - most allocations are extremely cheap -- no malloc(), no atomic ops
00181  * - deallocations are also cheap -- one atomic op
00182  * - completely immune to the ABA problem 
00183  * - memory can be redistributed among threads between bursts
00184 
00185  * CONS:
00186  *
00187  * - each thread must have its own allocator, which means managing
00188  *   thread-local storage (if compilers ever support non-POD __thread
00189  *   objects, this problem would go away).
00190  *
00191  * - though threads attempt to keep their caches reasonably-sized,
00192  *   they will only do so at allocation or thread destruction time,
00193  *   leading to potential hoarding
00194  *
00195  * - memory leaks (or unexpectedly long-lived objects) are extremly
00196  *   expensive because they keep a whole block from being freed
00197  *   instead of just one object. However, the remaining chips of each
00198  *   block are at least available for reuse. 
00199  */
00200 
00201 template<class T, class Pool=dynpool, size_t MaxBytes=0>
00202 struct block_pool 
00203 {
00204 
00205     typedef memory_block::meta_block_size<sizeof(T)> BlockSize;
00206 
00207     // use functions because dbx can't see enums very well
00208     static size_t block_size() { return BlockSize::BYTES; }
00209     static size_t chip_count() { return BlockSize::COUNT; }
00210     static size_t chip_size()  { return sizeof(T); }
00211 
00212     /**\brief Each block_pool<T> has its own allocator, one per type T.  */
00213     static Pool* get_static_pool() {
00214         /*
00215          * The instances of block_pool are thread-local but the function-local
00216          * static variable p is not.  There is one global instance per type T, which
00217          * endures for the life of the program so it cannot be destructed when
00218          * the instances of memory_block::block_list that use it are destroyed.
00219         */
00220         static Pool p(chip_size(), chip_count(), BlockSize::LOG2,
00221               MaxBytes? MaxBytes : 1024*1024*1024);
00222 
00223         return &p;
00224     }
00225   
00226     block_pool()
00227     : _blist(get_static_pool(), chip_size(), chip_count(), block_size())
00228     , _acquires(0)
00229     {
00230     }
00231     /**\brief Acquire one object(chip) from the pool.  */
00232     void* acquire() {
00233         _acquires++;
00234         memory_block::chip_t result =
00235         _blist.acquire(chip_size(), chip_count(), block_size());
00236         return result.v;
00237         // Note that it acquires one chip, not chip_count.  
00238     }
00239     
00240     /* Verify that we own the object then find its block and report it
00241        as released. If \e destruct is \e true then call the object's
00242        desctructor also.
00243      */
00244     static
00245     void release(void* ptr) {
00246         w_assert0(get_static_pool()->validate_pointer(ptr));
00247         memory_block::chip_t chip = {ptr};
00248         memory_block::block_of_chips::release_chip(chip, chip_size(), chip_count(), block_size());
00249     }
00250 
00251     void recycle() {
00252         _blist.recycle(chip_size(), chip_count(), block_size());
00253     }
00254     void dump() const {
00255         // chip releases cannot be counted since release() is a static method.
00256         fprintf(stderr, "DUMP ---> block_pool<%s> %p acquires %d \n", 
00257                 typeid(T).name(),
00258                 (void*)this,
00259                 _acquires);
00260         _blist.dump();
00261     }
00262 
00263     size_t freelist_size() const { return _blist.pool_freelist_size(); }
00264     
00265 private:
00266     memory_block::block_list _blist;
00267     int _acquires;
00268 };
00269 
00270 /**\brief Per-thread per-type heap. 
00271  * 
00272  * 
00273  * Caller must apply constructor, using normal
00274  * syntax for new, but with 
00275  * friend template, as follows:
00276  * \code
00277  * obj = new (*pool) T(args);
00278  * \endcode
00279  *
00280  * To delete an object, client uses
00281  * \code
00282  * pool->destroy_object(obj)
00283  * \endcode
00284  * which applies the
00285  * destructor and then releases the object back to the pool.
00286  * 
00287  */
00288 template<class T, size_t MaxBytes=0>
00289 struct block_alloc {
00290     
00291     typedef block_pool<T, dynpool, MaxBytes> Pool;
00292 
00293  /**\brief Let the zombies (released-but-not-yet-reusable chips) become usable again.
00294   */
00295     void recycle() { _pool.recycle(); }
00296 
00297     // This is the way the objects will be destroyed by the client
00298     // -- not through delete or any release*.
00299     static
00300     void destroy_object(T* ptr) {
00301         if(ptr == NULL) return;
00302 
00303         ptr->~T();
00304         Pool::release(ptr);  // static method
00305         // that releases the ptr into a static pool
00306         // (of type dynpool)
00307     }
00308 
00309     size_t freelist_size() const { return _pool.freelist_size(); }
00310     
00311 private:
00312     Pool _pool;
00313     
00314     // let operator new/delete access alloc()
00315     friend void* operator new<>(size_t, block_alloc<T,MaxBytes> &);
00316     friend void  operator delete<>(void*, block_alloc<T,MaxBytes> &);
00317 };
00318 
00319 template<class T, size_t MaxBytes>
00320 inline
00321 void* operator new(size_t nbytes, block_alloc<T,MaxBytes> &alloc) 
00322 {
00323     (void) nbytes; // keep gcc happy
00324     w_assert1(nbytes == sizeof(T));
00325     return alloc._pool.acquire();
00326 }
00327 
00328 /* No, this isn't a way to do "placement delete" (if only the language
00329    allowed that symmetry)... this operator is only called -- by the
00330    compiler -- if T's constructor throws
00331  */
00332 template<class T, size_t MaxBytes>
00333 inline
00334 void operator delete(void* ptr, block_alloc<T,MaxBytes> & /*alloc*/) 
00335 {
00336     if(ptr == NULL) return;
00337     block_alloc<T,MaxBytes>::Pool::release(ptr);
00338     w_assert2(0); // let a debug version catch this.
00339 }
00340 
00341 /*------------------------------------------------------------------------*/
00342 
00343 // prototype for the object cache TFactory...
00344 // (In fact this is not used in any specialization.
00345 // Only one specialization of the object cache is used and it 
00346 // is with object_cache_initializing factory.)
00347 template<class T>
00348 struct object_cache_default_factory {
00349     // these first three are required... the template args are optional
00350     static T*
00351     construct(memory_block::chip_t ptr) { return new (ptr.v) T; }
00352     static T*
00353     construct(void *ptr) { return new (ptr) T; }
00354 
00355     static void
00356     reset(T* t) { /* do nothing */ }
00357 
00358     static T*
00359     init(T* t) { /* do nothing */ return t; }
00360 };
00361 
00362 template<class T>
00363 struct object_cache_initializing_factory {
00364     // these first three are required... the template args are optional
00365     static T*
00366     construct(memory_block::chip_t ptr) { return new (ptr.v) T; }
00367     static T*
00368     construct(void *ptr) { return new (ptr) T; }
00369     
00370     static void
00371     reset(T* t) { t->reset(); }
00372 
00373     static T*
00374     init(T* t) { t->init(); return t; }
00375 
00376     // matched by object_cache::acquire below, but with the extra first T* arg...
00377     template<class Arg1>
00378     static T* init(T* t, Arg1 arg1) { t->init(arg1); return t; }
00379     template<class Arg1, class Arg2>
00380     static T* init(T* t, Arg1 arg1, Arg2 arg2) { t->init(arg1, arg2); return t; }    
00381     template<class Arg1, class Arg2, class Arg3>
00382     static T* init(T* t, Arg1 arg1, Arg2 arg2, Arg3 arg3) { t->init(arg1, arg2, arg3); return t; }
00383 };
00384 
00385 
00386 // object_cache: acquire calls T::T() **and** T::init() on the object acquired from pool.
00387 // release calls T::reset() on the object before releasing it to the pool.
00388 // T::reset() is assumed to make the object ready for re-use; the class T cannot require a
00389 // destructor call. This is an essential difference between an object_cache and a
00390 // block_alloc.
00391 template <class T, class TFactory=object_cache_default_factory<T>, size_t MaxBytes=0>
00392 struct object_cache {
00393 
00394     object_cache() {
00395     }
00396     ~object_cache() {
00397     }
00398     
00399     // for convenience... make sure to extend the object_cache_default_factory to match!!!
00400     T* acquire() {
00401         T* initialized = TFactory::init(_acquire());
00402         w_assert1(initialized != NULL);
00403         return initialized;
00404     }
00405     template<class Arg1>
00406     T* acquire(Arg1 arg1) {
00407         T* initialized = TFactory::init(_acquire(), arg1);
00408         w_assert1(initialized != NULL);
00409         return initialized;
00410     }
00411     template<class Arg1, class Arg2>
00412     T* acquire(Arg1 arg1, Arg2 arg2) {
00413         T* initialized = TFactory::init(_acquire(), arg1, arg2);
00414         w_assert1(initialized != NULL);
00415         return initialized;
00416     }    
00417     template<class Arg1, class Arg2, class Arg3>
00418     T* acquire(Arg1 arg1, Arg2 arg2, Arg3 arg3) {
00419         T* initialized = TFactory::init(_acquire(), arg1, arg2, arg3);
00420         w_assert1(initialized != NULL);
00421         return initialized;
00422     }
00423     
00424     T* _acquire() {
00425     // constructed when its block was allocated...
00426         union { void* v; T* t; } u = {_pool.acquire()};
00427         w_assert1(u.t != NULL);
00428         return u.t;
00429     }
00430 
00431     static
00432     void release(T* obj) {
00433         TFactory::reset(obj);
00434         Pool::release(obj);
00435     }
00436 
00437     void recycle() { _pool.recycle(); }
00438     void dump() const {
00439         fprintf(stderr, 
00440         "\nDUMP object_cache<%s> %p\n" , typeid(T).name(), (void*)this);
00441         _pool.dump();
00442     }
00443 
00444     size_t freelist_size() const { return _pool.freelist_size(); }
00445 
00446 private:
00447     
00448     struct cache_pool : public dynpool {
00449 
00450         // just a pass-thru...
00451         NORET cache_pool(size_t cs, size_t cc, size_t l2bs, size_t mb)
00452             : dynpool(cs, cc, l2bs, mb)
00453         {
00454         }
00455         virtual void _release_block(mblock* b);
00456         virtual mblock* _acquire_block();
00457         virtual NORET ~cache_pool();
00458     };
00459 
00460     typedef block_pool<T, cache_pool, MaxBytes> Pool;
00461     
00462     Pool _pool;
00463 
00464 
00465 };
00466 
00467 template <class T, class TF, size_t M>
00468 inline
00469 void object_cache<T,TF,M>::cache_pool::_release_block(mblock* b) {
00470     union { cache_pool* cp; memory_block::block_list* bl; } u={this};
00471     b->_owner = u.bl;
00472     dynpool::_release_block(b);
00473 }
00474     
00475 /* Intercept untagged (= newly-allocated) blocks in order to
00476    construct the objects they contain.
00477 */
00478 template <class T, class TF, size_t M>
00479 inline
00480 dynpool::mblock* object_cache<T,TF,M>::cache_pool::_acquire_block() {
00481     dynpool::mblock* b = dynpool::_acquire_block();
00482     void* me = this;
00483     if(me != b->_owner) {
00484         // Could be nil (if released)
00485         // -- or belong to another list?
00486         // new block -- initialize its objects : T::T() on each chip (j) of chip_size()
00487         for(size_t j=0; j < Pool::chip_count(); j++)  {
00488             memory_block::chip_t chip = b->get(j, Pool::chip_size());
00489             T *constructed = TF::construct(chip.v);
00490             w_assert0(constructed != NULL);
00491         }
00492         b->_owner = 0;
00493     }
00494     return b;
00495 }
00496 
00497 /* Destruct all cached objects before going down
00498  */
00499 template <class T, class TF, size_t M>
00500 inline
00501 NORET object_cache<T,TF,M>::cache_pool::~cache_pool() {
00502     size_t size = _size();
00503     for(size_t i=0; i < size; i++) {
00504         mblock* b = _at(i);
00505         for(size_t j=0; j < Pool::chip_count(); j++) {
00506             memory_block::chip_t chip = b->get(j, Pool::chip_size());
00507             union { char* c; T* t; } u = {chip.c};
00508             u.t->~T();
00509         }
00510     }
00511 }
00512 
00513 #endif

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