source/core/BufferAllocator.cpp (608 lines of code) (raw):

// // BufferAllocator.cpp // MNN // // Created by MNN on 2018/12/30. // Copyright © 2018, Alibaba Group Holding Limited // #include <string> #include "core/BufferAllocator.hpp" #include "core/Macro.h" #include "MNNFileUtils.h" // #define DUMP_USAGE //#define MNN_DEBUG_MEMORY namespace MNN { // MemChunk function bool MemChunk::invalid() const { return mNode == nullptr && first == nullptr; } void* MemChunk::base() const { if (mNode) { return mNode->base; } return first; } MemChunk MemChunk::operator+ (size_t offset) { auto chunk = *this; chunk.second += offset; return chunk; } size_t MemChunk::offset() const { if (mNode) { return mNode->offset + second; } return second; } void MemChunk::attach(Tensor* tensor) { if (mNode) { mNode->tensors.push_back(tensor); } } ErrorCode BufferAllocator::compute() { return NO_ERROR; } ErrorCode BufferAllocator::apply() { return NO_ERROR; } class DefaultAllocator : public BufferAllocator::Allocator { public: DefaultAllocator() { // Do nothing } virtual ~ DefaultAllocator() { // Do nothing } virtual MemChunk onAlloc(size_t size, size_t align) { return MemChunk(MNNMemoryAllocAlign(size, MNN_MEMORY_ALIGN_DEFAULT), 0); } virtual void onRelease(MemChunk chunk) { MNN_ASSERT(chunk.second == 0); MNNMemoryFreeAlign(chunk.first); } }; class MmapAllocator : public BufferAllocator::Allocator { private: std::map<void*, std::tuple<file_t,size_t, std::string>> mCache; std::string mFileName; std::string mPrefix; std::string mPosfix; int mAllocTimes = 0; bool mRemove; bool mNewMmap = false; public: MmapAllocator(const char* dirName, const char* prefix, const char* posfix, bool autoRemove) { if (nullptr != dirName) { mFileName = dirName; if (!MNNCreateDir(dirName)) { MNN_ERROR("%s not exist\n", dirName); } } if (nullptr != prefix) { mPrefix = prefix; } if (nullptr != posfix) { mPosfix = posfix; } mRemove = autoRemove; } virtual ~ MmapAllocator() { for (auto& iter : mCache) { MNNUnmapFile(iter.first, std::get<1>(iter.second)); MNNCloseFile(std::get<0>(iter.second)); if (mRemove) { MNNRemoveFile(std::get<2>(iter.second).c_str()); } } } virtual MemChunk onAlloc(size_t size, size_t align) override { MNN_ASSERT(size > 0); std::string name = mPrefix + std::to_string(mAllocTimes) + "." + mPosfix; std::string fileName = MNNFilePathConcat(mFileName, name); file_t file; if (MNNFileExist(fileName.c_str())) { file = MNNOpenFile(fileName.c_str(), MNN_FILE_READ | MNN_FILE_WRITE); } else { file = MNNCreateFile(fileName.c_str()); size = UP_DIV(size, align) * align; auto code = MNNSetFileSize(file, size); if (NO_ERROR != code) { MNN_ERROR("Set File size %lu error= %d\n", size, code); } mNewMmap = true; } void* ptr = MNNMmapFile(file, size); mCache.insert(std::make_pair(ptr, std::make_tuple(file, size, fileName))); mAllocTimes++; return MemChunk(ptr, 0); } virtual void onRelease(MemChunk chunk) override { MNN_ASSERT(chunk.second == 0); auto iter = mCache.find(chunk.first); if (iter == mCache.end()) { MNN_ASSERT(false); MNN_ERROR("Invalid free for MMAPAllocator\n"); return; } MNNUnmapFile(iter->first, std::get<1>(iter->second)); MNNCloseFile(std::get<0>(iter->second)); if (mRemove) { MNNRemoveFile(std::get<2>(iter->second).c_str()); } mCache.erase(iter); mAllocTimes = 0; } virtual void sync() override { if (!mRemove && mNewMmap) { for (auto& iter : mCache) { MNNMmapSync(iter.first, std::get<1>(iter.second)); } std::string cacheName = mPrefix + "sync." + mPosfix; std::string fileName = MNNFilePathConcat(mFileName, cacheName); MNNCreateFile(fileName.c_str()); } } }; class RecurseAllocator : public BufferAllocator::Allocator { public: RecurseAllocator(BufferAllocator* parent) { mParent = parent; } virtual ~ RecurseAllocator() { // Do nothing } virtual MemChunk onAlloc(size_t size, size_t align) override { return mParent->alloc(size, false, align); } virtual void onRelease(MemChunk chunk) override { mParent->free(chunk); } private: BufferAllocator* mParent; }; std::shared_ptr<BufferAllocator::Allocator> BufferAllocator::Allocator::createDefault() { std::shared_ptr<BufferAllocator::Allocator> _res; _res.reset(new DefaultAllocator); return _res; } std::shared_ptr<BufferAllocator::Allocator> BufferAllocator::Allocator::createMmap(const char* dirName, const char* prefix, const char* posfix, bool autoRemove) { std::shared_ptr<BufferAllocator::Allocator> _res; _res.reset(new MmapAllocator(dirName, prefix, posfix, autoRemove)); return _res; } std::shared_ptr<BufferAllocator::Allocator> BufferAllocator::Allocator::createRecurse(BufferAllocator* parent) { std::shared_ptr<BufferAllocator::Allocator> _res; _res.reset(new RecurseAllocator(parent)); return _res; } EagerBufferAllocator::Node::~Node() { if (nullptr == parent.get()) { outside->onRelease(pointer); } } MemChunk EagerBufferAllocator::alloc(size_t size, bool separate, size_t align) { #ifdef DUMP_USAGE auto memoryUsed = size / 1024.0f / 1024.0f; MNN_PRINT("Alloc: %f\n", memoryUsed); #endif if (0 == align) { align = mAlign; } std::pair<void*, size_t> pointer; // reuse if possible if (!separate) { if (nullptr != mCurrentFreeList) { pointer = getFromFreeList(mCurrentFreeList, size, false, align); } if (nullptr != pointer.first) { return MemChunk(pointer); } pointer = getFromFreeList(&mFreeList, size, true, align); if (nullptr != pointer.first) { return MemChunk(pointer); } } auto allocSize = size; if (mMinAllocSize != 0) { allocSize = ALIMAX(mMinAllocSize, size); } // alloc otherwise auto chunk = mAllocator->onAlloc(allocSize, align); pointer.first = chunk.first; pointer.second = chunk.second; if (nullptr == pointer.first) { return chunk; } mTotalSize += allocSize; // save node SharedPtr<Node> node(new Node); node->size = allocSize; node->pointer = pointer; node->outside = mAllocator.get(); MNN_ASSERT(pointer.second % align == 0); if (allocSize > size) { // Split SharedPtr<Node> first(new Node); first->parent = node; first->size = size; first->pointer = pointer; mUsedList.insert(std::make_pair(pointer, first)); node->useCount = 1; SharedPtr<Node> second(new Node); second->parent = node; second->size = allocSize - size; second->pointer.first = pointer.first; second->pointer.second = pointer.second + size; if (nullptr != mCurrentFreeList) { mCurrentFreeList->insert(std::make_pair(second->size, second)); } else { mFreeList.insert(std::make_pair(second->size, second)); } } else { mUsedList[pointer] = node; } #ifdef DUMP_USAGE MNN_PRINT("mTotalSize: %f\n", mTotalSize / 1024.0f / 1024.0f); #endif return pointer; } void EagerBufferAllocator::returnMemory(FREELIST* listP, SharedPtr<Node> node, bool permitMerge) { auto& list = *listP; list.insert(std::make_pair(node->size, node)); // update parent use count if (nullptr != node->parent.get() && permitMerge) { auto parent = node->parent; parent->useCount -= 1; // merge if all subnodes were freed auto needMerge = parent->useCount == 0; while (needMerge) { // collect all subnodes for (auto iter = list.begin(); iter != list.end();) { if (iter->second->parent.get() == parent.get()) { iter = list.erase(iter); continue; } iter++; } // do merge downside up list.insert(std::make_pair(parent->size, parent)); needMerge = false; if (parent->parent.get() != nullptr) { parent = parent->parent; parent->useCount -= 1; needMerge = parent->useCount == 0; } } } } bool EagerBufferAllocator::free(MemChunk chunk) { std::pair<void*, size_t> pointer(chunk.first, chunk.second); // get node auto x = mUsedList.find(pointer); if (x == mUsedList.end()) { MNN_ASSERT(false); return false; } // mark as reusable auto node = x->second; mUsedList.erase(x); if (nullptr != mCurrentFreeList) { returnMemory(mCurrentFreeList, node, false); } else { returnMemory(&mFreeList, node); } #ifdef DUMP_USAGE if (node.get()) { auto memoryUsed = node->size / 1024.0f / 1024.0f; MNN_PRINT("Free: %f\n", memoryUsed); } #endif return true; } void EagerBufferAllocator::release(bool allRelease) { MNN_ASSERT(mGroups.empty()); if (allRelease) { mUsedList.clear(); mFreeList.clear(); mTotalSize = 0; return; } for (auto f : mFreeList) { if (f.second->parent.get() == nullptr) { MNN_ASSERT(mTotalSize >= f.first); mTotalSize -= f.first; } } mFreeList.clear(); } void EagerBufferAllocator::barrierBegin() { MNN_ASSERT(mGroups.empty()); } void EagerBufferAllocator::barrierEnd() { for (auto& freeGroup : mGroups) { auto freeList = *freeGroup; for (auto& iter : freeList) { returnMemory(&mFreeList, iter.second); } } mGroups.clear(); } void EagerBufferAllocator::beginGroup() { std::shared_ptr<FREELIST> newFreeList(new FREELIST); mCurrentFreeList = newFreeList.get(); mGroups.emplace_back(newFreeList); } void EagerBufferAllocator::endGroup() { mCurrentFreeList = nullptr; } void EagerBufferAllocator::sync() { mAllocator->sync(); } std::pair<void*, size_t> EagerBufferAllocator::getFromFreeList(FREELIST* list, size_t size, bool permiteSplit, size_t align) { #ifdef MNN_DEBUG_MEMORY return std::make_pair(nullptr, 0); #endif size_t realSize = size; bool needExtraSize = mAlign % align != 0; if (needExtraSize) { realSize = size + align - 1; } // get node larger than size auto x = list->lower_bound(realSize); if (x == list->end()) { return std::make_pair(nullptr, 0); } // update parent use count auto pointer = x->second->pointer; // Align offset if (needExtraSize) { size_t originOffset = pointer.second; pointer.second = UP_DIV(originOffset, align) * align; realSize = size + pointer.second - originOffset; } if (permiteSplit && nullptr != x->second->parent.get()) { x->second->parent->useCount += 1; } // uses up all aligned space auto sizeAlign = UP_DIV(realSize, mAlign) * mAlign; if (sizeAlign >= x->first || (!permiteSplit)) { mUsedList.insert(std::make_pair(pointer, x->second)); list->erase(x); MNN_ASSERT(pointer.second % align == 0); return pointer; } // split otherwise SharedPtr<Node> first(new Node); first->parent = x->second; first->size = sizeAlign; first->pointer = x->second->pointer; mUsedList.insert(std::make_pair(pointer, first)); x->second->useCount += 1; SharedPtr<Node> second(new Node); second->parent = x->second; second->size = x->second->size - sizeAlign; second->pointer.first = x->second->pointer.first; second->pointer.second = x->second->pointer.second + sizeAlign; list->erase(x); list->insert(std::make_pair(second->size, second)); MNN_ASSERT(pointer.second % align == 0); return pointer; } static void _CPUMemChunkApplyToTensor(uint8_t* ptr, size_t offset, Tensor* t) { t->buffer().host = ptr + offset; } SingleBufferWithAllocator::~ SingleBufferWithAllocator() { release(); } void SingleBufferWithAllocator::release() { if (current.first != nullptr) { root->onRelease(current); current.first = nullptr; current.second = 0; currentSize = 0; } } ErrorCode SingleBufferWithAllocator::realloc(size_t size, size_t align) { if (currentSize < size) { if (nullptr != current.first) { root->onRelease(current); } current = root->onAlloc(size, align); if (current.first == nullptr) { return OUT_OF_MEMORY; } currentSize = size; } return NO_ERROR; } DeferBufferAllocator::DeferBufferAllocator(SingleBufferWithAllocator* root, size_t align, MemChunkApplyToTensor func) : mAlign(align) { if (nullptr == func) { mApplyFunction = _CPUMemChunkApplyToTensor; } else { mApplyFunction = func; } mParent = root; } //------------------------------- DeferBufferAllocator -----------------------------------// MemChunk DeferBufferAllocator::alloc(size_t size, bool separate, size_t align) { if (0 == align) { align = mAlign; } size = UP_DIV(size, align) * align; if (mFreeList.empty() || separate) { auto newChunk = createMemNode(size); insert_after(newChunk); #ifdef DUMP_USAGE MNN_PRINT("Defer alloc: %p, %d\n", newChunk, size); #endif return MemChunk(newChunk); } std::unique_ptr<MemNode> tmpChunk(new MemNode(size)); auto iter = mFreeList.lower_bound(ChunkBySize(tmpChunk.get())); if (iter == mFreeList.end()) { --iter; } auto selectChunk = iter->chunk; mFreeList.erase(iter); selectChunk->usage = true; if (selectChunk->size > size) { // split `[####]` to `[###]->[#]` auto restChunk = createMemNode(selectChunk->size - size); restChunk->usage = false; insert_after(restChunk, selectChunk); // add `[#]` to freelist insertFree(restChunk); } // equal no change; small expand selectChunk->size = size; #ifdef DUMP_USAGE MNN_PRINT("Defer alloc: %p, %d\n", selectChunk, size); #endif return MemChunk(selectChunk); } bool DeferBufferAllocator::free(MemChunk chunk) { #ifdef DUMP_USAGE MNN_PRINT("Defer free: %p\n", chunk.mNode); #endif if (mBarrrier) { mBarrrierFreeChunks.emplace_back(std::move(chunk)); return true; } auto node = chunk.mNode; if (!node) { return false; } auto left = node->left; auto right = node->right; if (left && !left->usage) { // fuse to left eraseFree(left); node = fuse_to_left(left, node); } if (right && !right->usage) { // fuse to left eraseFree(right); node = fuse_to_left(node, right); } node->usage = false; insertFree(node); return true; } void DeferBufferAllocator::release(bool allRelease) { if (allRelease) { reset(); } } void DeferBufferAllocator::barrierBegin() { MNN_ASSERT(!mBarrrier); mBarrrier = true; } void DeferBufferAllocator::barrierEnd() { mBarrrier = false; MNN_ASSERT(!mBarrrier); for (auto& chunk : mBarrrierFreeChunks) { this->free(chunk); } mBarrrierFreeChunks.clear(); } void DeferBufferAllocator::beginGroup() { // do nothing } void DeferBufferAllocator::endGroup() { // do nothing } void DeferBufferAllocator::reset() { mTotalSize = 0; mChunks.clear(); mFreeList.clear(); mPtr.first = nullptr; mPtr.second = 0; mHead = nullptr; mTail = nullptr; mBarrrier = false; mBarrrierFreeChunks.clear(); } ErrorCode DeferBufferAllocator::compute() { if (mTotalSize > 0) { return NO_ERROR; } mTotalSize = 0; if (mFreeList.empty()) { return NO_ERROR; } MNN_ASSERT(mFreeList.size() == 1); MNN_ASSERT(mHead == mTail); if (mFreeList.size() != 1 || mHead != mTail) { // Defer allocator compute error return INVALID_VALUE; } auto chunk = mHead; while (chunk) { chunk->offset = mTotalSize; visiChildren(chunk); mTotalSize += chunk->size; chunk = chunk->right; } return apply(); } ErrorCode DeferBufferAllocator::apply() { if (mFreeList.empty()) { // Not alloc return NO_ERROR; } auto& chunk = mParent->current; bool needApply = false; if (mParent->currentSize < mTotalSize) { needApply = true; auto code = mParent->realloc(mTotalSize, mAlign); if (NO_ERROR != code) { return code; } } else if (mPtr.first != chunk.first || mPtr.second != chunk.second) { needApply = true; } if (!needApply) { return NO_ERROR; } mPtr = chunk; for (auto& chunk : mChunks) { chunk->base = mPtr.ptr(); for (auto t : chunk->tensors) { mApplyFunction((uint8_t*)mPtr.base(), chunk->offset + mPtr.offset(), t); } } return NO_ERROR; } // some utils functions of DeferBufferAllocator void DeferBufferAllocator::visiChildren(MemNode* chunk) { if (!chunk) return; for (auto child : chunk->children) { child->offset += chunk->offset; visiChildren(child); } } MemNode* DeferBufferAllocator::fuse_to_left(MemNode* left, MemNode* right) { right->offset = left->size; left->size += right->size; left->children.push_back(right); erase_node(right); return left; } void DeferBufferAllocator::erase_node(MemNode* chunk) { auto left = chunk->left; auto right = chunk->right; if (left && right) { left->right = right; right->left = left; return; } if (left) { left->right = nullptr; mTail = left; return; } if (right) { right->left = nullptr; mHead = right; return; } mHead = mTail = nullptr; } void DeferBufferAllocator::insert_after(MemNode* chunk, MemNode* pos) { if (pos) { auto right = pos->right; if (right) { right->left = chunk; } chunk->right = right; chunk->left = pos; pos->right = chunk; if (pos == mTail) { mTail = chunk; } } else if (mTail) { mTail->right = chunk; chunk->left = mTail; mTail = chunk; } else { mHead = chunk; mTail = chunk; } } MemNode* DeferBufferAllocator::createMemNode(size_t size) { mChunks.emplace_back(new MemNode(size)); return mChunks.back().get(); } void DeferBufferAllocator::insertFree(MemNode* chunk) { mFreeList.insert(ChunkBySize(chunk)); } void DeferBufferAllocator::eraseFree(MemNode* chunk) { auto range = mFreeList.equal_range(ChunkBySize(chunk)); for (auto iter = range.first; iter != range.second; iter++) { if (iter->chunk == chunk) { mFreeList.erase(iter); break; } } } } // namespace MNN