Pyrogenesis  trunk
allocator_policies.h
Go to the documentation of this file.
1 /* Copyright (c) 2011 Wildfire Games
2  *
3  * Permission is hereby granted, free of charge, to any person obtaining
4  * a copy of this software and associated documentation files (the
5  * "Software"), to deal in the Software without restriction, including
6  * without limitation the rights to use, copy, modify, merge, publish,
7  * distribute, sublicense, and/or sell copies of the Software, and to
8  * permit persons to whom the Software is furnished to do so, subject to
9  * the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included
12  * in all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21  */
22 
23 /*
24  * policy class templates for allocators.
25  */
26 
27 #ifndef ALLOCATOR_POLICIES
28 #define ALLOCATOR_POLICIES
29 
30 #include "lib/alignment.h" // pageSize
33 
34 
35 namespace Allocators {
36 
37 
38 //-----------------------------------------------------------------------------
39 // Growth
40 
41 // O(N) allocations, O(1) wasted space.
42 template<size_t increment = pageSize>
44 {
45  size_t operator()(size_t oldSize) const
46  {
47  return oldSize + increment;
48  }
49 };
50 
51 // O(log r) allocations, O(N) wasted space. NB: the common choice of
52 // expansion factor r = 2 (e.g. in the GCC STL) prevents
53 // Storage_Reallocate from reusing previous memory blocks,
54 // thus constantly growing the heap and decreasing locality.
55 // Alexandrescu [C++ and Beyond 2010] recommends r < 33/25.
56 // we approximate this with a power of two divisor to allow shifting.
57 // C++ does allow reference-to-float template parameters, but
58 // integer arithmetic is expected to be faster.
59 // (Storage_Commit should use 2:1 because it is cheaper to
60 // compute and retains power-of-two sizes.)
61 template<size_t multiplier = 21, size_t divisor = 16>
63 {
64  size_t operator()(size_t oldSize) const
65  {
66  const size_t product = oldSize * multiplier;
67 
68  // detect overflow, but allow equality in case oldSize = 0,
69  // which isn't a problem because Storage_Commit::Expand
70  // raises it to requiredCapacity.
71  ASSERT(product >= oldSize);
72 
73  return product / divisor;
74  }
75 };
76 
77 
78 //-----------------------------------------------------------------------------
79 // Storage
80 
81 // a contiguous region of memory (not just an "array", because
82 // allocators such as Arena append variable-sized intervals).
83 //
84 // we don't store smart pointers because storage usually doesn't need
85 // to be copied, and ICC 11 sometimes wasn't able to inline Address().
86 struct Storage
87 {
88  // @return starting address (alignment depends on the allocator).
89  uintptr_t Address() const;
90 
91  // @return size [bytes] of currently accessible memory.
92  size_t Capacity() const;
93 
94  // @return largest possible capacity [bytes].
95  size_t MaxCapacity() const;
96 
97  // expand Capacity() to at least requiredCapacity (possibly more
98  // depending on GrowthPolicy).
99  // @param requiredCapacity > Capacity()
100  // @return false and leave Capacity() unchanged if expansion failed,
101  // which is guaranteed to happen if requiredCapacity > MaxCapacity().
102  bool Expand(size_t requiredCapacity);
103 };
104 
105 
106 // allocate once and refuse subsequent expansion.
107 template<class Allocator = Allocator_Aligned<> >
109 {
111 public:
112  Storage_Fixed(size_t size)
113  : maxCapacity(size)
114  , storage(allocator.allocate(maxCapacity))
115  {
116  }
117 
119  {
120  allocator.deallocate(storage, maxCapacity);
121  }
122 
123  uintptr_t Address() const
124  {
125  return uintptr_t(storage);
126  }
127 
128  size_t Capacity() const
129  {
130  return maxCapacity;
131  }
132 
133  size_t MaxCapacity() const
134  {
135  return maxCapacity;
136  }
137 
138  bool Expand(size_t UNUSED(requiredCapacity))
139  {
140  return false;
141  }
142 
143 private:
145  size_t maxCapacity; // must be initialized before storage
146  void* storage;
147 };
148 
149 
150 // unlimited expansion by allocating larger storage and copying.
151 // (basically equivalent to std::vector, although Growth_Exponential
152 // is much more cache and allocator-friendly than the GCC STL)
153 template<class Allocator = Allocator_Heap, class GrowthPolicy = Growth_Exponential<> >
155 {
157 public:
158  Storage_Reallocate(size_t initialCapacity)
159  : capacity(initialCapacity)
160  , storage(allocator.allocate(initialCapacity))
161  {
162  }
163 
165  {
166  allocator.deallocate(storage, capacity);
167  }
168 
169  uintptr_t Address() const
170  {
171  return uintptr_t(storage);
172  }
173 
174  size_t Capacity() const
175  {
176  return capacity;
177  }
178 
179  size_t MaxCapacity() const
180  {
181  return std::numeric_limits<size_t>::max();
182  }
183 
184  bool Expand(size_t requiredCapacity)
185  {
186  size_t newCapacity = std::max(requiredCapacity, GrowthPolicy()(capacity));
187  void* newStorage = allocator.allocate(newCapacity);
188  if(!newStorage)
189  return false;
190  memcpy(newStorage, storage, capacity);
191  std::swap(capacity, newCapacity);
192  std::swap(storage, newStorage);
193  allocator.deallocate(newStorage, newCapacity); // free PREVIOUS storage
194  return true;
195  }
196 
197 private:
199  size_t capacity; // must be initialized before storage
200  void* storage;
201 };
202 
203 
204 // expand up to the limit of the allocated address space by
205 // committing physical memory. this avoids copying and
206 // reduces wasted physical memory.
207 template<class Allocator = Allocator_AddressSpace<>, class GrowthPolicy = Growth_Exponential<2,1> >
209 {
211 public:
212  Storage_Commit(size_t maxCapacity_)
213  : maxCapacity(Align<pageSize>(maxCapacity_)) // see Expand
214  , storage(allocator.allocate(maxCapacity))
215  , capacity(0)
216  {
217  }
218 
220  {
221  allocator.deallocate(storage, maxCapacity);
222  }
223 
224  uintptr_t Address() const
225  {
226  return uintptr_t(storage);
227  }
228 
229  size_t Capacity() const
230  {
231  return capacity;
232  }
233 
234  size_t MaxCapacity() const
235  {
236  return maxCapacity;
237  }
238 
239  bool Expand(size_t requiredCapacity)
240  {
241  size_t newCapacity = std::max(requiredCapacity, GrowthPolicy()(capacity));
242  // reduce the number of expensive commits by accurately
243  // reflecting the actual capacity. this is safe because
244  // we also round up maxCapacity.
245  newCapacity = Align<pageSize>(newCapacity);
246  if(newCapacity > maxCapacity)
247  return false;
248  if(!vm::Commit(Address()+capacity, newCapacity-capacity))
249  return false;
250  capacity = newCapacity;
251  return true;
252  }
253 
254 private:
256  size_t maxCapacity; // must be initialized before storage
257  void* storage;
258  size_t capacity;
259 };
260 
261 
262 // implicitly expand up to the limit of the allocated address space by
263 // committing physical memory when a page is first accessed.
264 // this is basically equivalent to Storage_Commit with Growth_Linear,
265 // except that there is no need to call Expand.
266 template<class Allocator = Allocator_AddressSpace<> >
268 {
270 public:
271  Storage_AutoCommit(size_t maxCapacity_)
272  : maxCapacity(Align<pageSize>(maxCapacity_)) // match user's expectation
273  , storage(allocator.allocate(maxCapacity))
274  {
276  }
277 
279  {
281  allocator.deallocate(storage, maxCapacity);
282  }
283 
284  uintptr_t Address() const
285  {
286  return uintptr_t(storage);
287  }
288 
289  size_t Capacity() const
290  {
291  return maxCapacity;
292  }
293 
294  size_t MaxCapacity() const
295  {
296  return maxCapacity;
297  }
298 
299  bool Expand(size_t UNUSED(requiredCapacity))
300  {
301  return false;
302  }
303 
304 private:
306  size_t maxCapacity; // must be initialized before storage
307  void* storage;
308 };
309 
310 
311 // reserve and return a pointer to space at the end of storage,
312 // expanding it if need be.
313 // @param end total number of previously reserved bytes; will be
314 // increased by size if the allocation succeeds.
315 // @param size [bytes] to reserve.
316 // @return address of allocated space, or 0 if storage is full
317 // and cannot expand any further.
318 template<class Storage>
319 static inline uintptr_t StorageAppend(Storage& storage, size_t& end, size_t size)
320 {
321  size_t newEnd = end + size;
322  if(newEnd > storage.Capacity())
323  {
324  if(!storage.Expand(newEnd)) // NB: may change storage.Address()
325  return 0;
326  }
327 
328  std::swap(end, newEnd);
329  return storage.Address() + newEnd;
330 }
331 
332 
333 // invoke operator() on default-constructed instantiations of
334 // Functor for reasonable combinations of Storage and their parameters.
335 template<template<class Storage> class Functor>
336 static void ForEachStorage()
337 {
338  Functor<Storage_Fixed<Allocator_Heap> >()();
339  Functor<Storage_Fixed<Allocator_Aligned<> > >()();
340 
341  Functor<Storage_Reallocate<Allocator_Heap, Growth_Linear<> > >()();
342  Functor<Storage_Reallocate<Allocator_Heap, Growth_Exponential<> > >()();
343  Functor<Storage_Reallocate<Allocator_Aligned<>, Growth_Linear<> > >()();
344  Functor<Storage_Reallocate<Allocator_Aligned<>, Growth_Exponential<> > >()();
345 
346  Functor<Storage_Commit<Allocator_AddressSpace<>, Growth_Linear<> > >()();
347  Functor<Storage_Commit<Allocator_AddressSpace<>, Growth_Exponential<> > >()();
348 
349  Functor<Storage_AutoCommit<> >()();
350 }
351 
352 } // namespace Allocators
353 
354 #endif // #ifndef ALLOCATOR_POLICIES
#define NONCOPYABLE(className)
Indicates that a class is noncopyable (usually due to const or reference members, or because the clas...
Definition: code_annotation.h:217
uintptr_t Address() const
Definition: allocator_policies.h:224
size_t operator()(size_t oldSize) const
Definition: allocator_policies.h:45
Storage_AutoCommit(size_t maxCapacity_)
Definition: allocator_policies.h:271
static const size_t pageSize
Definition: alignment.h:83
#define UNUSED(param)
mark a function parameter as unused and avoid the corresponding compiler warning. ...
Definition: code_annotation.h:38
size_t operator()(size_t oldSize) const
Definition: allocator_policies.h:64
void BeginOnDemandCommits()
install a handler that attempts to commit memory whenever a read/write page fault is encountered...
Definition: uvm.cpp:120
size_t Capacity() const
Definition: allocator_policies.h:174
~Storage_Fixed()
Definition: allocator_policies.h:118
size_t Align(size_t n)
Definition: alignment.h:36
Definition: allocator_policies.h:62
uintptr_t Address() const
Definition: allocator_policies.h:123
size_t MaxCapacity() const
Definition: allocator_policies.h:179
size_t Capacity() const
Definition: file_cache.cpp:91
uintptr_t Address() const
Definition: allocator_policies.h:284
~Storage_Reallocate()
Definition: allocator_policies.h:164
static void swap(UniqueRange &p1, UniqueRange &p2)
Definition: unique_range.h:198
size_t MaxCapacity() const
Definition: allocator_policies.h:294
#define ASSERT(expr)
same as ENSURE in debug mode, does nothing in release mode.
Definition: debug.h:315
Definition: allocator_policies.h:267
bool Expand(size_t requiredCapacity)
Definition: allocator_policies.h:43
bool Commit(uintptr_t address, size_t size, PageType pageType, int prot)
map physical memory to previously reserved address space.
Definition: uvm.cpp:59
static uintptr_t StorageAppend(Storage &storage, size_t &end, size_t size)
Definition: allocator_policies.h:319
Storage_Commit(size_t maxCapacity_)
Definition: allocator_policies.h:212
~Storage_Commit()
Definition: allocator_policies.h:219
size_t Capacity() const
Definition: allocator_policies.h:289
static void ForEachStorage()
Definition: allocator_policies.h:336
Storage_Reallocate(size_t initialCapacity)
Definition: allocator_policies.h:158
size_t capacity
Definition: allocator_policies.h:258
size_t maxCapacity
Definition: allocator_policies.h:306
void EndOnDemandCommits()
decrements the reference count begun by BeginOnDemandCommit and removes the page fault handler when i...
Definition: uvm.cpp:125
Definition: allocator_policies.h:208
uintptr_t Address() const
Definition: allocator_policies.h:169
void * storage
Definition: allocator_policies.h:146
~Storage_AutoCommit()
Definition: allocator_policies.h:278
size_t Capacity() const
Definition: allocator_policies.h:229
Allocator allocator
Definition: allocator_policies.h:305
size_t MaxCapacity() const
Definition: allocator_policies.h:133
Definition: allocator_policies.h:154
Definition: allocator_policies.h:35
Allocator allocator
Definition: allocator_policies.h:144
Storage_Fixed(size_t size)
Definition: allocator_policies.h:112
bool Expand(size_t requiredCapacity)
Definition: allocator_policies.h:138
size_t maxCapacity
Definition: allocator_policies.h:145
Allocator allocator
Definition: allocator_policies.h:255
size_t Capacity() const
Definition: allocator_policies.h:128
bool Expand(size_t requiredCapacity)
Definition: allocator_policies.h:299
void * storage
Definition: allocator_policies.h:307
void * storage
Definition: allocator_policies.h:200
bool Expand(size_t requiredCapacity)
Definition: allocator_policies.h:239
Definition: allocator_policies.h:86
Allocator allocator
Definition: allocator_policies.h:198
bool Expand(size_t requiredCapacity)
Definition: allocator_policies.h:184
size_t MaxCapacity() const
Definition: allocator_policies.h:234
uintptr_t Address() const
size_t maxCapacity
Definition: allocator_policies.h:256
void * storage
Definition: allocator_policies.h:257
size_t capacity
Definition: allocator_policies.h:199
Definition: allocator_policies.h:108