Zero  0.1.0
mem_block.h
Go to the documentation of this file.
1 /* -*- mode:C++; c-basic-offset:4 -*-
2  Shore-MT -- Multi-threaded port of the SHORE storage manager
3 
4  Copyright (c) 2007-2009
5  Data Intensive Applications and Systems Labaratory (DIAS)
6  Ecole Polytechnique Federale de Lausanne
7 
8  All Rights Reserved.
9 
10  Permission to use, copy, modify and distribute this software and
11  its documentation is hereby granted, provided that both the
12  copyright notice and this permission notice appear in all copies of
13  the software, derivative works or modified versions, and any
14  portions thereof, and that both notices appear in supporting
15  documentation.
16 
17  This code is distributed in the hope that it will be useful, but
18  WITHOUT ANY WARRANTY; without even the implied warranty of
19  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. THE AUTHORS
20  DISCLAIM ANY LIABILITY OF ANY KIND FOR ANY DAMAGES WHATSOEVER
21  RESULTING FROM THE USE OF THIS SOFTWARE.
22 */
23 /*<std-header orig-src='shore' incl-file-exclusion='MEM_BLOCK_H'>
24 
25  $Id: mem_block.h,v 1.4 2010/12/08 17:37:37 nhall Exp $
26 
27 SHORE -- Scalable Heterogeneous Object REpository
28 
29 Copyright (c) 1994-99 Computer Sciences Department, University of
30  Wisconsin -- Madison
31 All Rights Reserved.
32 
33 Permission to use, copy, modify and distribute this software and its
34 documentation is hereby granted, provided that both the copyright
35 notice and this permission notice appear in all copies of the
36 software, derivative works or modified versions, and any portions
37 thereof, and that both notices appear in supporting documentation.
38 
39 THE AUTHORS AND THE COMPUTER SCIENCES DEPARTMENT OF THE UNIVERSITY
40 OF WISCONSIN - MADISON ALLOW FREE USE OF THIS SOFTWARE IN ITS
41 "AS IS" CONDITION, AND THEY DISCLAIM ANY LIABILITY OF ANY KIND
42 FOR ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
43 
44 This software was developed with support by the Advanced Research
45 Project Agency, ARPA order number 018 (formerly 8230), monitored by
46 the U.S. Army Research Laboratory under contract DAAB07-91-C-Q518.
47 Further funding for this work was provided by DARPA through
48 Rome Research Laboratory Contract No. F30602-97-2-0247.
49 
50 */
51 
52 #ifndef __MEM_BLOCK_H
53 #define __MEM_BLOCK_H
54 
56 #include <cstddef>
57 #include <cassert>
58 #include "w_base.h"
59 
60 /* NOTE: The classes defined here are non-template helpers for the
61  template-based block_pool class. Because they are only intended to
62  be used from template code, they accept "template" parameters with
63  every call instead of storing them internally: register-passing
64  from the template code is less expensive than loading the value
65  from memory, and also minimizes per-block space overhead.
66  */
67 
68 namespace memory_block {
69 #if 0 /*keep emacs happy */
70  } // keep emacs happy...
71 #endif
72 
73 /* GCC can't handle zero length arrays, but has an extension to handle
74  unsized ones at the end of a struct. Meanwhile, SunStudio can't
75  handle unsized arrays, but has an extension to allow zero-length
76  arrays.
77  */
78 #ifdef __GNUC__
79 #define EMPTY_ARRAY_DIM
80 #else
81 #define EMPTY_ARRAY_DIM 0
82 #endif
83 
84 // forward decl...
85  struct block_list;
86 
87 /* A bitmap allocator.
88 
89  This class tracks the bookkeeping of chip allocation while leaving
90  the corresponding memory management to someone else. The
91  implementation requires that chip allocation be single-threaded
92  (presumably by some owner thread), but is thread-safe with respect
93  to deallocation.
94 
95  A given "chip" may be in one of three states:
96 
97  USABLE: available to be allocated
98 
99  IN-USE: allocated but not yet freed
100 
101  ZOMBIE: freed more recently than the last recycling pass
102 
103  Allocation is double-buffered in a sense: at the beginning of each
104  allocation round, the owning thread unions the current set of
105  zombie chips into the usable set; in-use chips are ignored.
106 
107  The class has two members to support higher-level functionality:
108 
109  _owner: an opaque pointer which is used to verify that blocks
110  are being released properly
111 
112  _next: an embedded linked list node for use by the owner and
113  otherwise ignored by the implementation
114  */
115  struct block_bits {
116 
117  typedef uint64_t bitmap;
118 
119  enum {
120  MAX_CHIPS = 8 * sizeof(bitmap)
121  };
122 
123  block_bits(size_t chip_count);
124 
125  size_t acquire_contiguous(size_t chip_count);
126 
127  // @return whether released successfully
128  bool release_contiguous(size_t idx, size_t chip_count);
129 
130  static bitmap create_mask(size_t bits_set);
131 
132  size_t usable_count() const {
133  return _popc(_usable_chips);
134  }
135 
136  size_t zombie_count() const {
137  return _popc(_zombie_chips);
138  }
139 
140  void recycle();
141 
142  static size_t _popc(bitmap bm);
143 
144  bitmap _usable_chips; // available as of last recycling (1thr)
145  bitmap _zombie_chips; // deallocations since last recycling (racy)
146  };
147 
148 /* Control structure for one block of allocatable memory.
149 
150  The memory is assumed to be /block_size/ bytes long and must be
151  aligned on a /block_size/-sized boundary. The implementation
152  exploits the block's alignment to compute the block control for
153  pointers being released, meaning there is no per-chip space
154  overhead.
155 
156  One caveat of the implicit control block pointer approach is that
157  the caller is responsible to ensure that any chip passed to
158  /release()/ is actually part of a block (presumably by verifying
159  that the address range is inside an appropriate memory pool).
160 
161  This class manages memory only very loosely: distributing and
162  accepting the return of /chip_size/-sized chips. At no time does it
163  access any of the memory it manages.
164  */
165  struct block {
166  block(size_t chip_size, size_t chip_count, size_t block_size);
167 
168  void* acquire_chip(size_t chip_size, size_t chip_count, size_t block_size);
169 
170  // WARNING: the caller must ensure that ptr is in a valid memory range
171  // @return: whether successfully released
172  static bool release_chip(void* ptr, size_t chip_size, size_t chip_count, size_t block_size);
173 
174  // WARNING: the caller must ensure that ptr is in a valid memory range
175  static void release(void* ptr, size_t chip_size, size_t chip_count, size_t block_size);
176 
177  void recycle() {
178  _bits.recycle();
179  }
180 
181  char* _get(size_t idx, size_t chip_size);
182 
183  block_bits _bits;
184 
185  block_list* _owner;
186 
187  block* _next;
188 
189  // The memory managed:
190  // char _data[EMPTY_ARRAY_DIM];
191  char _data[0];
192  };
193 
194  struct block_pool {
195  block* acquire_block(block_list* owner);
196 
197  block* release_block(block* b);
198 
199  // true if /ptr/ points to data inside some block managed by this pool
200  virtual bool validate_pointer(void* ptr) = 0;
201 
202  virtual ~block_pool() {}
203 
204  protected:
205 
206  virtual block* _acquire_block() = 0;
207 
208  // takes back b, then returns b->_next
209  virtual void _release_block(block* b) = 0;
210  };
211 
212  struct block_list {
213  block_list(block_pool* pool, size_t chip_size, size_t chip_count,
214  size_t block_size);
215 
216  ~block_list();
217 
218  void* acquire(size_t chip_size, size_t chip_count, size_t block_size);
219 
220  block* acquire_block(size_t block_size);
221 
222  void* _slow_acquire(size_t chip_size, size_t chip_count, size_t block_size);
223 
224  void _change_blocks(size_t chip_size, size_t chip_count, size_t block_size);
225 
226  block* _tail;
227 
228  block_pool* _pool;
229 
230  size_t _hit_count;
231 
232  double _avg_hit_rate;
233 
234  block _fake_block;
235  };
236 
237  inline
238  block* block_pool::acquire_block(block_list* owner) {
239  block* b = _acquire_block();
240  b->_owner = owner;
241  b->_next = 0;
242  b->_bits.recycle();
243  return b;
244  }
245 
246  inline
247  block* block_pool::release_block(block* b) {
248  assert(validate_pointer(b));
249  block* next = b->_next;
250  b->_next = 0;
251  b->_owner = 0;
252  _release_block(b);
253  return next;
254  }
255 
256 /* A compile-time predicate.
257 
258  Compilation will fail for B=false because only B=true has a definition
259 */
260  template<bool B>
261  struct fail_unless;
262 
263  template<>
264  struct fail_unless<true> {
265  static bool valid() {
266  return true;
267  }
268  };
269 
270 /* A compile-time bounds checker.
271 
272  Fails to compile unless /L <= N <= U/
273  */
274  template<int N, int L, int U>
275  struct bounds_check : public fail_unless<(L <= N) && (N <= U)> {
276  static bool valid() {
277  return true;
278  }
279  };
280 
281 /* A template class which, given some positive compile-time constant
282  integer /x/, computes the compile-time constant value of
283  /floor(log2(x))/
284  */
285 
286  template<int N>
287  struct meta_log2 : public fail_unless<(N > 2)> {
288  enum {
289  VALUE = 1 + meta_log2<N / 2>::VALUE
290  };
291  };
292 
293  template<>
294  struct meta_log2<2> {
295  enum {
296  VALUE = 1
297  };
298  };
299 
300  template<>
301  struct meta_log2<1> {
302  enum {
303  VALUE = 0
304  };
305  };
306 
307  template<int A, int B>
308  struct meta_min {
309  enum {
310  VALUE = (A < B) ? A : B
311  };
312  };
313 
314 /* A template class which computes the optimal block size. Too large
315  a block and the bitmap can't reach all the objects that fit,
316  wasting space. Too small and the bitmap is mostly empty, leading to
317  high overheads (in both space and time).
318 
319  For any given parameters there exists only one value of /BYTES/
320  which is a power of two and utilizes 50% < util <= 100% of a
321  block_bit's chips.
322  */
323  template<int ChipSize, int OverheadBytes = sizeof(memory_block::block), int MaxChips = block_bits::MAX_CHIPS>
324  struct meta_block_size : public fail_unless<(ChipSize > 0 && OverheadBytes >= 0)> {
325  enum {
326  CHIP_SIZE = ChipSize
327  };
328  enum {
329  OVERHEAD = OverheadBytes
330  };
331  enum {
332  MAX_CHIPS = MaxChips
333  };
334  enum {
335  MIN_CHIPS = MAX_CHIPS / 2 + 1
336  };
337  enum {
338  BYTES_NEEDED = MIN_CHIPS * ChipSize + OverheadBytes
339  };
340  enum {
341  LOG2 = meta_log2<2 * BYTES_NEEDED - 1>::VALUE
342  };
343 
344  enum {
345  BYTES = 1 << LOG2
346  };
347  fail_unless<((BYTES & -BYTES) == BYTES)> power_of_two;
348 
349  /* ugly corner case...
350 
351  if chips are small compared to overhead then we can end up with
352  space for more than MAX_CHIPS. However, cutting the block size
353  in half wouldn't leave enough chips behind so we're stuck.
354 
355  Note that this wasted space would be small compared with the
356  enormous overhead that causes the situation in the first place.
357  */
358  enum {
359  REAL_COUNT = (BYTES - OverheadBytes) / ChipSize
360  };
361  fail_unless<((OVERHEAD + MIN_CHIPS * ChipSize) > (int)BYTES / 2)> sane_chip_count;
362 
363  enum {
364  COUNT = meta_min<MAX_CHIPS, REAL_COUNT>::VALUE
365  };
366  bounds_check<COUNT, MIN_CHIPS, MAX_CHIPS> bounds;
367  };
368 } // namespace memory_block
369 
372 #endif // __MEM_BLOCK_H