summaryrefslogtreecommitdiff
path: root/runtimes/nn/depend/external/gemmlowp/internal/allocator.h
diff options
context:
space:
mode:
Diffstat (limited to 'runtimes/nn/depend/external/gemmlowp/internal/allocator.h')
-rw-r--r--runtimes/nn/depend/external/gemmlowp/internal/allocator.h220
1 files changed, 220 insertions, 0 deletions
diff --git a/runtimes/nn/depend/external/gemmlowp/internal/allocator.h b/runtimes/nn/depend/external/gemmlowp/internal/allocator.h
new file mode 100644
index 000000000..da325a4c4
--- /dev/null
+++ b/runtimes/nn/depend/external/gemmlowp/internal/allocator.h
@@ -0,0 +1,220 @@
+// 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"
+
+#if defined(__ANDROID__)
+#include <android/api-level.h>
+// The 18 here should be 16, but has to be 18 for now due
+// to a Google-internal issue.
+#if __ANDROID_API__ < 18
+#include <malloc.h>
+#define GEMMLOWP_USE_MEMALIGN
+#endif
+// posix_memalign is missing on some 4.1 x86 devices
+#if __ANDROID_API__ == 18
+#ifdef GEMMLOWP_X86_32
+#include <malloc.h>
+#define GEMMLOWP_USE_MEMALIGN
+#endif
+#endif
+#endif
+
+namespace gemmlowp {
+
+enum class TypeId : std::uint8_t { Uint8, Int8, Uint16, Int16, Uint32, Int32 };
+
+template <typename T>
+struct GetTypeIdImpl {};
+
+template <typename T>
+inline TypeId GetTypeId() {
+ return GetTypeIdImpl<T>::Value;
+}
+
+template <typename T>
+struct GetTypeIdImpl<const T> : GetTypeIdImpl<T> {};
+
+#define GEMMLOWP_REGISTER_TYPEID(type_, id) \
+ template <> \
+ struct GetTypeIdImpl<type_> { \
+ 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 const 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 const std::size_t kMaxBlocks = 5;
+
+ void Commit() {
+ assert(!committed_);
+
+ if (reserved_bytes_ > storage_size_) {
+ DeallocateStorage();
+ storage_size_ = RoundUpToPowerOfTwo(reserved_bytes_);
+#ifdef GEMMLOWP_USE_MEMALIGN
+ storage_ = memalign(kAlignment, storage_size_);
+#else
+ if (posix_memalign(&storage_, kAlignment, storage_size_)) {
+ storage_ = nullptr;
+ }
+#endif
+ }
+
+ 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 <typename T>
+ 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<kAlignment>(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<T>();
+
+ reserved_blocks_++;
+ reserved_bytes_ += bytes;
+
+ return h;
+ }
+
+ // Returns the pointer to the allocated buffer for the given handle.
+ // Must be called after committing.
+ template <typename T>
+ 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<T>() && "type mismatch");
+ std::size_t offset = reserved_blocks_offsets_[h.index_];
+ std::uintptr_t addr = reinterpret_cast<std::uintptr_t>(storage_) + offset;
+ return reinterpret_cast<T*>(addr);
+ }
+
+ private:
+ void DeallocateStorage() {
+ assert(!committed_);
+ 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_