1 // Copyright 2015 The Gemmlowp Authors. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // allocator.h: a buffer allocator that allows avoiding most of the 16 // malloc/free overhead, by: 17 // 1. Requiring all N allocations to be reserved in advance, and 18 // then commited at once, turning N allocations into 1. 19 // 2. Being persistent, the allocated storage is reused across commits, 20 // and only reallocated as needed when the commit size gets larger. 21 // 22 // This is driven by Android-specific needs: 23 // 1. On Android, the default (Bionic) allocator tends to aggressively 24 // unmap pages, which means that malloc/free can be surprisingly expensive. 25 // 2. On Android, stack allocations with alloca() can't be as large as on 26 // desktop platforms. 27 // 28 // General usage: 29 // 1. Reserve blocks by calling Reserve(), which returns a Handle. 30 // 2. Call Commit() once. 31 // 3. Now it is possible to get pointers to allocated buffers by calling 32 // GetPointer(). 33 // 4. Call Decommit() once. 34 // 5. The allocator is now reverted to its original state, except that 35 // it retained its allocated storage, so the next Commit() will be faster. 36 // The allocated storage is only freed when the Allocator object is 37 // destroyed. 38 39 #ifndef GEMMLOWP_INTERNAL_ALLOCATOR_H_ 40 #define GEMMLOWP_INTERNAL_ALLOCATOR_H_ 41 42 #include "common.h" 43 44 namespace gemmlowp { 45 46 enum class TypeId : std::uint8_t { Uint8, Int8, Uint16, Int16, Uint32, Int32 }; 47 48 template <typename T> 49 struct GetTypeIdImpl {}; 50 51 template <typename T> 52 inline TypeId GetTypeId() { 53 return GetTypeIdImpl<T>::Value; 54 } 55 56 template <typename T> 57 struct GetTypeIdImpl<const T> : GetTypeIdImpl<T> {}; 58 59 #define GEMMLOWP_REGISTER_TYPEID(type_, id) \ 60 template <> \ 61 struct GetTypeIdImpl<type_> { \ 62 static const TypeId Value = TypeId::id; \ 63 }; 64 65 GEMMLOWP_REGISTER_TYPEID(std::uint8_t, Uint8) 66 GEMMLOWP_REGISTER_TYPEID(std::int8_t, Int8) 67 GEMMLOWP_REGISTER_TYPEID(std::uint16_t, Uint16) 68 GEMMLOWP_REGISTER_TYPEID(std::int16_t, Int16) 69 GEMMLOWP_REGISTER_TYPEID(std::uint32_t, Uint32) 70 GEMMLOWP_REGISTER_TYPEID(std::int32_t, Int32) 71 72 class Allocator { 73 public: 74 Allocator() 75 : committed_(false), 76 storage_size_(0), 77 storage_(nullptr), 78 reserved_blocks_(0), 79 reserved_bytes_(0), 80 generation_(0) {} 81 82 ~Allocator() { 83 assert(!committed_); 84 assert(!reserved_blocks_); 85 DeallocateStorage(); 86 } 87 88 // Alignment of allocated blocks. 89 static const std::size_t kAlignment = kDefaultCacheLineSize; 90 91 // This is all we need so far, and since the usage pattern is fixed, 92 // there is no point in allowing more until we need to. 93 static const std::size_t kMaxBlocks = 5; 94 95 void Commit() { 96 assert(!committed_); 97 98 if (reserved_bytes_ > storage_size_) { 99 DeallocateStorage(); 100 storage_size_ = RoundUpToPowerOfTwo(reserved_bytes_); 101 storage_ = aligned_alloc(kAlignment, storage_size_); 102 } 103 104 ReleaseBuildAssertion(!storage_size_ || storage_, "allocation failure"); 105 committed_ = true; 106 } 107 108 void Decommit() { 109 assert(committed_); 110 committed_ = false; 111 generation_++; 112 113 reserved_blocks_ = 0; 114 reserved_bytes_ = 0; 115 } 116 117 // See generation_ 118 typedef std::size_t generation_t; 119 120 // A handle on a reserved block. The user obtains 121 // one by calling Reserve() and, after committing, 122 // passes it to GetPointer(). 123 class Handle { 124 std::uint8_t index_; 125 generation_t generation_; 126 TypeId type_; 127 128 friend class Allocator; 129 }; 130 131 // Reserves a block sized for n elements of type T, and 132 // returns a handle to it. Must be called before committing. 133 template <typename T> 134 Handle Reserve(std::size_t n) { 135 assert(!committed_ && "can't reserve blocks while committed"); 136 assert(reserved_blocks_ < kMaxBlocks && 137 "didn't expect to allocate this many blocks"); 138 const std::size_t bytes = RoundUp<kAlignment>(n * sizeof(T)); 139 const std::size_t offset = reserved_bytes_; 140 const std::size_t index = reserved_blocks_; 141 142 reserved_blocks_offsets_[index] = offset; 143 Handle h; 144 h.index_ = index; 145 h.generation_ = generation_; 146 h.type_ = GetTypeId<T>(); 147 148 reserved_blocks_++; 149 reserved_bytes_ += bytes; 150 151 return h; 152 } 153 154 // Returns the pointer to the allocated buffer for the given handle. 155 // Must be called after committing. 156 template <typename T> 157 T* GetPointer(const Handle& h) const { 158 assert(committed_ && "can't get block pointers unless committed"); 159 assert(h.index_ < reserved_blocks_ && 160 "bad handle, points to inexistant block"); 161 assert(h.generation_ == generation_ && 162 "handle from earlier generation, have decommitted since"); 163 assert(h.type_ == GetTypeId<T>() && "type mismatch"); 164 std::size_t offset = reserved_blocks_offsets_[h.index_]; 165 std::uintptr_t addr = reinterpret_cast<std::uintptr_t>(storage_) + offset; 166 return reinterpret_cast<T*>(addr); 167 } 168 169 private: 170 void DeallocateStorage() { 171 assert(!committed_); 172 aligned_free(storage_); 173 storage_size_ = 0; 174 } 175 176 // Set to true by Commit() and to false by Decommit(). Initially false. 177 bool committed_; 178 179 // The actually allocated storage size and buffer pointer. 180 std::size_t storage_size_; 181 mutable void* storage_; 182 183 // The number of blocks that have been reserved by Reserve(). 184 std::size_t reserved_blocks_; 185 // The number of bytes that have been reserved by Reserve(). 186 std::size_t reserved_bytes_; 187 // The offsets of reserved blocks into the storage buffer. 188 std::size_t reserved_blocks_offsets_[kMaxBlocks]; 189 190 // The 'generation' is incremented on Decommit() and allows catching 191 // bad GetPointer() calls still referring to a previous commit. 192 generation_t generation_; 193 }; 194 195 } // namespace gemmlowp 196 197 #endif // GEMMLOWP_INTERNAL_ALLOCATOR_H_ 198