// Copyright 2015 The Gemmlowp Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // allocator.h: a buffer allocator that allows avoiding most of the // malloc/free overhead, by: // 1. Requiring all N allocations to be reserved in advance, and // then commited at once, turning N allocations into 1. // 2. Being persistent, the allocated storage is reused across commits, // and only reallocated as needed when the commit size gets larger. // // This is driven by Android-specific needs: // 1. On Android, the default (Bionic) allocator tends to aggressively // unmap pages, which means that malloc/free can be surprisingly expensive. // 2. On Android, stack allocations with alloca() can't be as large as on // desktop platforms. // // General usage: // 1. Reserve blocks by calling Reserve(), which returns a Handle. // 2. Call Commit() once. // 3. Now it is possible to get pointers to allocated buffers by calling // GetPointer(). // 4. Call Decommit() once. // 5. The allocator is now reverted to its original state, except that // it retained its allocated storage, so the next Commit() will be faster. // The allocated storage is only freed when the Allocator object is // destroyed. #ifndef GEMMLOWP_INTERNAL_ALLOCATOR_H_ #define GEMMLOWP_INTERNAL_ALLOCATOR_H_ #include "common.h" namespace gemmlowp { enum class TypeId : std::uint8_t { Uint8, Int8, Uint16, Int16, Uint32, Int32 }; template struct GetTypeIdImpl {}; template inline TypeId GetTypeId() { return GetTypeIdImpl::Value; } template struct GetTypeIdImpl : GetTypeIdImpl {}; #define GEMMLOWP_REGISTER_TYPEID(type_, id) \ template <> \ struct GetTypeIdImpl { \ static const TypeId Value = TypeId::id; \ }; GEMMLOWP_REGISTER_TYPEID(std::uint8_t, Uint8) GEMMLOWP_REGISTER_TYPEID(std::int8_t, Int8) GEMMLOWP_REGISTER_TYPEID(std::uint16_t, Uint16) GEMMLOWP_REGISTER_TYPEID(std::int16_t, Int16) GEMMLOWP_REGISTER_TYPEID(std::uint32_t, Uint32) GEMMLOWP_REGISTER_TYPEID(std::int32_t, Int32) class Allocator { public: Allocator() : committed_(false), storage_size_(0), storage_(nullptr), reserved_blocks_(0), reserved_bytes_(0), generation_(0) {} ~Allocator() { assert(!committed_); assert(!reserved_blocks_); DeallocateStorage(); } // Alignment of allocated blocks. static constexpr std::size_t kAlignment = kDefaultCacheLineSize; // This is all we need so far, and since the usage pattern is fixed, // there is no point in allowing more until we need to. static constexpr std::size_t kMaxBlocks = 5; void Commit() { assert(!committed_); if (reserved_bytes_ > storage_size_) { DeallocateStorage(); storage_size_ = RoundUpToPowerOfTwo(reserved_bytes_); storage_ = aligned_alloc(kAlignment, storage_size_); } ReleaseBuildAssertion(!storage_size_ || storage_, "allocation failure"); committed_ = true; } void Decommit() { assert(committed_); committed_ = false; generation_++; reserved_blocks_ = 0; reserved_bytes_ = 0; } // See generation_ typedef std::size_t generation_t; // A handle on a reserved block. The user obtains // one by calling Reserve() and, after committing, // passes it to GetPointer(). class Handle { std::uint8_t index_; generation_t generation_; TypeId type_; friend class Allocator; }; // Reserves a block sized for n elements of type T, and // returns a handle to it. Must be called before committing. template Handle Reserve(std::size_t n) { assert(!committed_ && "can't reserve blocks while committed"); assert(reserved_blocks_ < kMaxBlocks && "didn't expect to allocate this many blocks"); const std::size_t bytes = RoundUp(n * sizeof(T)); const std::size_t offset = reserved_bytes_; const std::size_t index = reserved_blocks_; reserved_blocks_offsets_[index] = offset; Handle h; h.index_ = index; h.generation_ = generation_; h.type_ = GetTypeId(); reserved_blocks_++; reserved_bytes_ += bytes; return h; } // Returns the pointer to the allocated buffer for the given handle. // Must be called after committing. template T* GetPointer(const Handle& h) const { assert(committed_ && "can't get block pointers unless committed"); assert(h.index_ < reserved_blocks_ && "bad handle, points to inexistant block"); assert(h.generation_ == generation_ && "handle from earlier generation, have decommitted since"); assert(h.type_ == GetTypeId() && "type mismatch"); std::size_t offset = reserved_blocks_offsets_[h.index_]; std::uintptr_t addr = reinterpret_cast(storage_) + offset; return reinterpret_cast(addr); } private: void DeallocateStorage() { assert(!committed_); aligned_free(storage_); storage_size_ = 0; } // Set to true by Commit() and to false by Decommit(). Initially false. bool committed_; // The actually allocated storage size and buffer pointer. std::size_t storage_size_; mutable void* storage_; // The number of blocks that have been reserved by Reserve(). std::size_t reserved_blocks_; // The number of bytes that have been reserved by Reserve(). std::size_t reserved_bytes_; // The offsets of reserved blocks into the storage buffer. std::size_t reserved_blocks_offsets_[kMaxBlocks]; // The 'generation' is incremented on Decommit() and allows catching // bad GetPointer() calls still referring to a previous commit. generation_t generation_; }; } // namespace gemmlowp #endif // GEMMLOWP_INTERNAL_ALLOCATOR_H_