/* Copyright (c) 2011 Wildfire Games * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* * policy class templates for allocators. */ #ifndef ALLOCATOR_POLICIES #define ALLOCATOR_POLICIES #include "lib/alignment.h" // pageSize #include "lib/allocators/allocator_adapters.h" #include "lib/allocators/freelist.h" namespace Allocators { //----------------------------------------------------------------------------- // Growth // O(N) allocations, O(1) wasted space. template struct Growth_Linear { size_t operator()(size_t oldSize) const { return oldSize + increment; } }; // O(log r) allocations, O(N) wasted space. NB: the common choice of // expansion factor r = 2 (e.g. in the GCC STL) prevents // Storage_Reallocate from reusing previous memory blocks, // thus constantly growing the heap and decreasing locality. // Alexandrescu [C++ and Beyond 2010] recommends r < 33/25. // we approximate this with a power of two divisor to allow shifting. // C++ does allow reference-to-float template parameters, but // integer arithmetic is expected to be faster. // (Storage_Commit should use 2:1 because it is cheaper to // compute and retains power-of-two sizes.) template struct Growth_Exponential { size_t operator()(size_t oldSize) const { const size_t product = oldSize * multiplier; // detect overflow, but allow equality in case oldSize = 0, // which isn't a problem because Storage_Commit::Expand // raises it to requiredCapacity. ASSERT(product >= oldSize); return product / divisor; } }; //----------------------------------------------------------------------------- // Storage // a contiguous region of memory (not just an "array", because // allocators such as Arena append variable-sized intervals). // // we don't store smart pointers because storage usually doesn't need // to be copied, and ICC 11 sometimes wasn't able to inline Address(). struct Storage { // @return starting address (alignment depends on the allocator). uintptr_t Address() const; // @return size [bytes] of currently accessible memory. size_t Capacity() const; // @return largest possible capacity [bytes]. size_t MaxCapacity() const; // expand Capacity() to at least requiredCapacity (possibly more // depending on GrowthPolicy). // @param requiredCapacity > Capacity() // @return false and leave Capacity() unchanged if expansion failed, // which is guaranteed to happen if requiredCapacity > MaxCapacity(). bool Expand(size_t requiredCapacity); }; // allocate once and refuse subsequent expansion. template > class Storage_Fixed { NONCOPYABLE(Storage_Fixed); public: Storage_Fixed(size_t size) : maxCapacity(size) , storage(allocator.allocate(maxCapacity)) { } ~Storage_Fixed() { allocator.deallocate(storage, maxCapacity); } uintptr_t Address() const { return uintptr_t(storage); } size_t Capacity() const { return maxCapacity; } size_t MaxCapacity() const { return maxCapacity; } bool Expand(size_t UNUSED(requiredCapacity)) { return false; } private: Allocator allocator; size_t maxCapacity; // must be initialized before storage void* storage; }; // unlimited expansion by allocating larger storage and copying. // (basically equivalent to std::vector, although Growth_Exponential // is much more cache and allocator-friendly than the GCC STL) template > class Storage_Reallocate { NONCOPYABLE(Storage_Reallocate); public: Storage_Reallocate(size_t initialCapacity) : capacity(initialCapacity) , storage(allocator.allocate(initialCapacity)) { } ~Storage_Reallocate() { allocator.deallocate(storage, capacity); } uintptr_t Address() const { return uintptr_t(storage); } size_t Capacity() const { return capacity; } size_t MaxCapacity() const { return std::numeric_limits::max(); } bool Expand(size_t requiredCapacity) { size_t newCapacity = std::max(requiredCapacity, GrowthPolicy()(capacity)); void* newStorage = allocator.allocate(newCapacity); if(!newStorage) return false; memcpy(newStorage, storage, capacity); std::swap(capacity, newCapacity); std::swap(storage, newStorage); allocator.deallocate(newStorage, newCapacity); // free PREVIOUS storage return true; } private: Allocator allocator; size_t capacity; // must be initialized before storage void* storage; }; // expand up to the limit of the allocated address space by // committing physical memory. this avoids copying and // reduces wasted physical memory. template, class GrowthPolicy = Growth_Exponential<2,1> > class Storage_Commit { NONCOPYABLE(Storage_Commit); public: Storage_Commit(size_t maxCapacity_) : maxCapacity(Align(maxCapacity_)) // see Expand , storage(allocator.allocate(maxCapacity)) , capacity(0) { } ~Storage_Commit() { allocator.deallocate(storage, maxCapacity); } uintptr_t Address() const { return uintptr_t(storage); } size_t Capacity() const { return capacity; } size_t MaxCapacity() const { return maxCapacity; } bool Expand(size_t requiredCapacity) { size_t newCapacity = std::max(requiredCapacity, GrowthPolicy()(capacity)); // reduce the number of expensive commits by accurately // reflecting the actual capacity. this is safe because // we also round up maxCapacity. newCapacity = Align(newCapacity); if(newCapacity > maxCapacity) return false; if(!vm::Commit(Address()+capacity, newCapacity-capacity)) return false; capacity = newCapacity; return true; } private: Allocator allocator; size_t maxCapacity; // must be initialized before storage void* storage; size_t capacity; }; // implicitly expand up to the limit of the allocated address space by // committing physical memory when a page is first accessed. // this is basically equivalent to Storage_Commit with Growth_Linear, // except that there is no need to call Expand. template > class Storage_AutoCommit { NONCOPYABLE(Storage_AutoCommit); public: Storage_AutoCommit(size_t maxCapacity_) : maxCapacity(Align(maxCapacity_)) // match user's expectation , storage(allocator.allocate(maxCapacity)) { vm::BeginOnDemandCommits(); } ~Storage_AutoCommit() { vm::EndOnDemandCommits(); allocator.deallocate(storage, maxCapacity); } uintptr_t Address() const { return uintptr_t(storage); } size_t Capacity() const { return maxCapacity; } size_t MaxCapacity() const { return maxCapacity; } bool Expand(size_t UNUSED(requiredCapacity)) { return false; } private: Allocator allocator; size_t maxCapacity; // must be initialized before storage void* storage; }; // reserve and return a pointer to space at the end of storage, // expanding it if need be. // @param end total number of previously reserved bytes; will be // increased by size if the allocation succeeds. // @param size [bytes] to reserve. // @return address of allocated space, or 0 if storage is full // and cannot expand any further. template static inline uintptr_t StorageAppend(Storage& storage, size_t& end, size_t size) { size_t newEnd = end + size; if(newEnd > storage.Capacity()) { if(!storage.Expand(newEnd)) // NB: may change storage.Address() return 0; } std::swap(end, newEnd); return storage.Address() + newEnd; } // invoke operator() on default-constructed instantiations of // Functor for reasonable combinations of Storage and their parameters. template class Functor> static void ForEachStorage() { Functor >()(); Functor > >()(); Functor > >()(); Functor > >()(); Functor, Growth_Linear<> > >()(); Functor, Growth_Exponential<> > >()(); Functor, Growth_Linear<> > >()(); Functor, Growth_Exponential<> > >()(); Functor >()(); } } // namespace Allocators #endif // #ifndef ALLOCATOR_POLICIES