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