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