/** * @file llmemory.cpp * @brief Very special memory allocation/deallocation stuff here * * $LicenseInfo:firstyear=2002&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #include "linden_common.h" #include "llthread.h" #if defined(LL_WINDOWS) //# include # include #elif defined(LL_DARWIN) # include # include # include #elif LL_LINUX || LL_SOLARIS # include #endif #include "llmemory.h" #include "llsys.h" #include "llframetimer.h" //---------------------------------------------------------------------------- //static char* LLMemory::reserveMem = 0; U32 LLMemory::sAvailPhysicalMemInKB = U32_MAX ; U32 LLMemory::sMaxPhysicalMemInKB = 0; U32 LLMemory::sAllocatedMemInKB = 0; U32 LLMemory::sAllocatedPageSizeInKB = 0 ; U32 LLMemory::sMaxHeapSizeInKB = U32_MAX ; BOOL LLMemory::sEnableMemoryFailurePrevention = FALSE; //static void LLMemory::initClass() { if (!reserveMem) { reserveMem = new char[16*1024]; // reserve 16K for out of memory error handling } } //static void LLMemory::cleanupClass() { delete [] reserveMem; reserveMem = NULL; } //static void LLMemory::freeReserve() { delete [] reserveMem; reserveMem = NULL; } //static void LLMemory::initMaxHeapSizeGB(F32 max_heap_size_gb, BOOL prevent_heap_failure) { sMaxHeapSizeInKB = (U32)(max_heap_size_gb * 1024 * 1024) ; sEnableMemoryFailurePrevention = prevent_heap_failure ; } //static void LLMemory::updateMemoryInfo() { #if LL_WINDOWS HANDLE self = GetCurrentProcess(); PROCESS_MEMORY_COUNTERS counters; if (!GetProcessMemoryInfo(self, &counters, sizeof(counters))) { llwarns << "GetProcessMemoryInfo failed" << llendl; return ; } sAllocatedMemInKB = (U32)(counters.WorkingSetSize / 1024) ; sAllocatedPageSizeInKB = (U32)(counters.PagefileUsage / 1024) ; sMaxPhysicalMemInKB = llmin(LLMemoryInfo::getAvailableMemoryKB() + sAllocatedMemInKB, sMaxHeapSizeInKB); if(sMaxPhysicalMemInKB > sAllocatedMemInKB) { sAvailPhysicalMemInKB = sMaxPhysicalMemInKB - sAllocatedMemInKB ; } else { sAvailPhysicalMemInKB = 0 ; } #else //not valid for other systems for now. sAllocatedMemInKB = (U32)(LLMemory::getCurrentRSS() / 1024) ; sMaxPhysicalMemInKB = U32_MAX ; sAvailPhysicalMemInKB = U32_MAX ; #endif return ; } // //this function is to test if there is enough space with the size in the virtual address space. //it does not do any real allocation //if success, it returns the address where the memory chunk can fit in; //otherwise it returns NULL. // //static void* LLMemory::tryToAlloc(void* address, U32 size) { #if LL_WINDOWS address = VirtualAlloc(address, size, MEM_RESERVE | MEM_TOP_DOWN, PAGE_NOACCESS) ; if(address) { if(!VirtualFree(address, 0, MEM_RELEASE)) { llerrs << "error happens when free some memory reservation." << llendl ; } } #else #endif return address ; } //static void LLMemory::logMemoryInfo(BOOL update) { if(update) { updateMemoryInfo() ; } llinfos << "Current allocated physical memory(KB): " << sAllocatedMemInKB << llendl ; llinfos << "Current allocated page size (KB): " << sAllocatedPageSizeInKB << llendl ; llinfos << "Current availabe physical memory(KB): " << sAvailPhysicalMemInKB << llendl ; llinfos << "Current max usable memory(KB): " << sMaxPhysicalMemInKB << llendl ; } //return 0: everything is normal; //return 1: the memory pool is low, but not in danger; //return -1: the memory pool is in danger, is about to crash. //static S32 LLMemory::isMemoryPoolLow() { static const U32 LOW_MEMEOY_POOL_THRESHOLD_KB = 64 * 1024 ; //64 MB for emergency use if(!sEnableMemoryFailurePrevention) { return 0 ; //no memory failure prevention. } if(sAvailPhysicalMemInKB < (LOW_MEMEOY_POOL_THRESHOLD_KB >> 2)) //out of physical memory { return -1 ; } if(sAllocatedPageSizeInKB + (LOW_MEMEOY_POOL_THRESHOLD_KB >> 2) > sMaxHeapSizeInKB) //out of virtual address space. { return -1 ; } return (S32)(sAvailPhysicalMemInKB < LOW_MEMEOY_POOL_THRESHOLD_KB || sAllocatedPageSizeInKB + LOW_MEMEOY_POOL_THRESHOLD_KB > sMaxHeapSizeInKB) ; } //static U32 LLMemory::getAvailableMemKB() { return sAvailPhysicalMemInKB ; } //static U32 LLMemory::getMaxMemKB() { return sMaxPhysicalMemInKB ; } //static U32 LLMemory::getAllocatedMemKB() { return sAllocatedMemInKB ; } void* ll_allocate (size_t size) { if (size == 0) { llwarns << "Null allocation" << llendl; } void *p = malloc(size); if (p == NULL) { LLMemory::freeReserve(); llerrs << "Out of memory Error" << llendl; } return p; } void ll_release (void *p) { free(p); } //---------------------------------------------------------------------------- #if defined(LL_WINDOWS) U64 LLMemory::getCurrentRSS() { HANDLE self = GetCurrentProcess(); PROCESS_MEMORY_COUNTERS counters; if (!GetProcessMemoryInfo(self, &counters, sizeof(counters))) { llwarns << "GetProcessMemoryInfo failed" << llendl; return 0; } return counters.WorkingSetSize; } #elif defined(LL_DARWIN) /* The API used here is not capable of dealing with 64-bit memory sizes, but is available before 10.4. Once we start requiring 10.4, we can use the updated API, which looks like this: task_basic_info_64_data_t basicInfo; mach_msg_type_number_t basicInfoCount = TASK_BASIC_INFO_64_COUNT; if (task_info(mach_task_self(), TASK_BASIC_INFO_64, (task_info_t)&basicInfo, &basicInfoCount) == KERN_SUCCESS) Of course, this doesn't gain us anything unless we start building the viewer as a 64-bit executable, since that's the only way for our memory allocation to exceed 2^32. */ // if (sysctl(ctl, 2, &page_size, &size, NULL, 0) == -1) // { // llwarns << "Couldn't get page size" << llendl; // return 0; // } else { // return page_size; // } // } U64 LLMemory::getCurrentRSS() { U64 residentSize = 0; task_basic_info_data_t basicInfo; mach_msg_type_number_t basicInfoCount = TASK_BASIC_INFO_COUNT; if (task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&basicInfo, &basicInfoCount) == KERN_SUCCESS) { residentSize = basicInfo.resident_size; // If we ever wanted it, the process virtual size is also available as: // virtualSize = basicInfo.virtual_size; // llinfos << "resident size is " << residentSize << llendl; } else { llwarns << "task_info failed" << llendl; } return residentSize; } #elif defined(LL_LINUX) U64 LLMemory::getCurrentRSS() { static const char statPath[] = "/proc/self/stat"; LLFILE *fp = LLFile::fopen(statPath, "r"); U64 rss = 0; if (fp == NULL) { llwarns << "couldn't open " << statPath << llendl; goto bail; } // Eee-yew! See Documentation/filesystems/proc.txt in your // nearest friendly kernel tree for details. { int ret = fscanf(fp, "%*d (%*[^)]) %*c %*d %*d %*d %*d %*d %*d %*d " "%*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %Lu", &rss); if (ret != 1) { llwarns << "couldn't parse contents of " << statPath << llendl; rss = 0; } } fclose(fp); bail: return rss; } #elif LL_SOLARIS #include #include #include #define _STRUCTURED_PROC 1 #include U64 LLMemory::getCurrentRSS() { char path [LL_MAX_PATH]; /* Flawfinder: ignore */ sprintf(path, "/proc/%d/psinfo", (int)getpid()); int proc_fd = -1; if((proc_fd = open(path, O_RDONLY)) == -1){ llwarns << "LLmemory::getCurrentRSS() unable to open " << path << ". Returning 0 RSS!" << llendl; return 0; } psinfo_t proc_psinfo; if(read(proc_fd, &proc_psinfo, sizeof(psinfo_t)) != sizeof(psinfo_t)){ llwarns << "LLmemory::getCurrentRSS() Unable to read from " << path << ". Returning 0 RSS!" << llendl; close(proc_fd); return 0; } close(proc_fd); return((U64)proc_psinfo.pr_rssize * 1024); } #else U64 LLMemory::getCurrentRSS() { return 0; } #endif //------------------------------------------------------------- //minimum block sizes (page size) for small allocation, medium allocation, large allocation const U32 MIN_BLOCK_SIZES[LLPrivateMemoryPool::SUPER_ALLOCATION] = {2 << 10, 4 << 10, 16 << 10} ; // //maximum block sizes for small allocation, medium allocation, large allocation const U32 MAX_BLOCK_SIZES[LLPrivateMemoryPool::SUPER_ALLOCATION] = {64 << 10, 1 << 20, 4 << 20} ; //minimum slot sizes for small allocation, medium allocation, large allocation const U32 MIN_SLOT_SIZES[LLPrivateMemoryPool::SUPER_ALLOCATION] = {8, 2 << 10, 512 << 10}; //maximum slot sizes for small allocation, medium allocation, large allocation const U32 MAX_SLOT_SIZES[LLPrivateMemoryPool::SUPER_ALLOCATION] = {(2 << 10) - 8, (512 - 2) << 10, 4 << 20}; //size of a block with multiple slots can not exceed CUT_OFF_SIZE const U32 CUT_OFF_SIZE = (64 << 10) ; //64 KB //------------------------------------------------------------- //class LLPrivateMemoryPool::LLMemoryBlock //------------------------------------------------------------- // //each memory block could fit for two page sizes: 0.75 * mSlotSize, which starts from the beginning of the memory chunk and grow towards the end of the //the block; another is mSlotSize, which starts from the end of the block and grows towards the beginning of the block. // LLPrivateMemoryPool::LLMemoryBlock::LLMemoryBlock() { //empty } LLPrivateMemoryPool::LLMemoryBlock::~LLMemoryBlock() { //empty } void LLPrivateMemoryPool::LLMemoryBlock::init(char* buffer, U32 buffer_size, U32 slot_size) { llassert_always(buffer_size >= slot_size) ; mBuffer = buffer ; mBufferSize = buffer_size ; mSlotSize = slot_size ; mTotalSlots = buffer_size / mSlotSize ; llassert_always(mTotalSlots < 256) ; //max number is 256 mAllocatedSlots = 0 ; //mark free bits S32 usage_bit_len = (mTotalSlots + 31) / 32 ; mDummySize = usage_bit_len - 1 ; if(mDummySize > 0) //extra space to store mUsageBits { mTotalSlots -= (mDummySize * sizeof(mUsageBits) + mSlotSize - 1) / mSlotSize ; usage_bit_len = (mTotalSlots + 31) / 32 ; mDummySize = usage_bit_len - 1 ; if(mDummySize > 0) { mUsageBits = 0 ; for(S32 i = 0 ; i < mDummySize ; i++) { *((U32*)mBuffer + i) = 0 ; } if(mTotalSlots & 31) { *((U32*)mBuffer + mDummySize - 1) = (0xffffffff << (mTotalSlots & 31)) ; } } } if(mDummySize < 1) { mUsageBits = 0 ; if(mTotalSlots & 31) { mUsageBits = (0xffffffff << (mTotalSlots & 31)) ; } } mSelf = this ; mNext = NULL ; mPrev = NULL ; llassert_always(mTotalSlots > 0) ; } void LLPrivateMemoryPool::LLMemoryBlock::setBuffer(char* buffer, U32 buffer_size) { llassert_always(buffer_size <= (16 << 20)) ; mBuffer = buffer ; mBufferSize = buffer_size ; mSelf = NULL ; mTotalSlots = 0 ; //set the block is free. } char* LLPrivateMemoryPool::LLMemoryBlock::allocate() { llassert_always(mAllocatedSlots < mTotalSlots) ; //find a free slot U32* bits = NULL ; U32 k = 0 ; if(mUsageBits != 0xffffffff) { bits = &mUsageBits ; } else if(mDummySize > 0)//go to extra space { for(S32 i = 0 ; i < mDummySize; i++) { if(*((U32*)mBuffer + i) != 0xffffffff) { bits = (U32*)mBuffer + i ; k = i + 1 ; break ; } } } S32 idx = 0 ; U32 tmp = *bits ; for(; tmp & 1 ; tmp >>= 1, idx++) ; //set the slot reserved if(!idx) { llassert_always(!(*bits & 1)); *bits |= 1 ; } else { llassert_always(!(*bits & (1 << idx))) ; *bits |= (1 << idx) ; } mAllocatedSlots++ ; //return mBuffer + mDummySize * sizeof(U32) + (k * 32 + idx) * mSlotSize ; char* p = mBuffer + mDummySize * sizeof(U32) + (k * 32 + idx) * mSlotSize ; llassert_always(mBuffer != p || !mDummySize) ; llassert_always(*(U32*)p == 0 && *((U32*)p + 1) == 0) ; return p ; } U32 col = 0, row = 0 ; void LLPrivateMemoryPool::LLMemoryBlock::free(void* addr) { llassert_always((U32)addr >= (U32)mBuffer + mDummySize * sizeof(U32) && (U32)addr < (U32)mBuffer + mBufferSize) ; U32 idx = ((U32)addr - (U32)mBuffer - mDummySize * sizeof(U32)) / mSlotSize ; llassert_always(idx < mTotalSlots) ; llassert_always(addr == mBuffer + mDummySize * sizeof(U32) + idx * mSlotSize) ; llassert_always(*(U32*)addr == col && *((U32*)addr + 1) == row) ; *(U32*)addr = 0 ; *((U32*)addr + 1) = 0 ; U32* bits = &mUsageBits ; if(idx >= 32) { bits = (U32*)mBuffer + (idx - 32) / 32 ; } if(idx & 31) { *bits &= ~(1 << (idx & 31)) ; } else { *bits &= ~1 ; } mAllocatedSlots-- ; } //for debug use void LLPrivateMemoryPool::LLMemoryBlock::resetBitMap() { for(S32 i = 0 ; i < mDummySize ; i++) { *((U32*)mBuffer + i) = 0 ; } mUsageBits = 0 ; } //------------------------------------------------------------------- //class LLMemoryChunk //-------------------------------------------------------------------- LLPrivateMemoryPool::LLMemoryChunk::LLMemoryChunk() { //empty } LLPrivateMemoryPool::LLMemoryChunk::~LLMemoryChunk() { //empty } void LLPrivateMemoryPool::LLMemoryChunk::init(char* buffer, U32 buffer_size, U32 min_slot_size, U32 max_slot_size, U32 min_block_size, U32 max_block_size) { mBuffer = buffer ; mBufferSize = buffer_size ; mAlloatedSize = 0 ; mMetaBuffer = mBuffer + sizeof(LLMemoryChunk) ; mMinBlockSize = min_block_size; mMinSlotSize = min_slot_size; mMaxSlotSize = max_slot_size ; mBlockLevels = mMaxSlotSize / mMinSlotSize ; mPartitionLevels = max_block_size / mMinBlockSize + 1 ; S32 max_num_blocks = (buffer_size - sizeof(LLMemoryChunk) - mBlockLevels * sizeof(LLMemoryBlock*) - mPartitionLevels * sizeof(LLMemoryBlock*)) / (mMinBlockSize + sizeof(LLMemoryBlock)) ; //meta data space mBlocks = (LLMemoryBlock*)mMetaBuffer ; mAvailBlockList = (LLMemoryBlock**)((char*)mBlocks + sizeof(LLMemoryBlock) * max_num_blocks) ; mFreeSpaceList = (LLMemoryBlock**)((char*)mAvailBlockList + sizeof(LLMemoryBlock*) * mBlockLevels) ; //data buffer mDataBuffer = (char*)mFreeSpaceList + sizeof(LLMemoryBlock*) * mPartitionLevels ; //init for(U32 i = 0 ; i < mBlockLevels; i++) { mAvailBlockList[i] = NULL ; } for(U32 i = 0 ; i < mPartitionLevels ; i++) { mFreeSpaceList[i] = NULL ; } mBlocks[0].mPrev = NULL ; mBlocks[0].mNext = NULL ; mBlocks[0].setBuffer(mDataBuffer, buffer_size - (mDataBuffer - mBuffer)) ; //debug U32 end = (mBlocks[0].getBufferSize() / mMinBlockSize) ; for(U32 i = 1 ; i < end ; i++) { mBlocks[i].mSelf = NULL ; } addToFreeSpace(&mBlocks[0]) ; mHashNext = NULL ; mNext = NULL ; mPrev = NULL ; } //static U32 LLPrivateMemoryPool::LLMemoryChunk::getMaxOverhead(U32 data_buffer_size, U32 min_page_size) { if(data_buffer_size / min_page_size < 64) //large allocations { return 4096 ; //4KB } else { return 0 ; //do not reserve extra overhead if for small allocations } } char* LLPrivateMemoryPool::LLMemoryChunk::allocate(U32 size) { char* p = NULL ; U32 blk_idx = getBlockLevel(size); LLMemoryBlock* blk = NULL ; //check if there is free block available if(mAvailBlockList[blk_idx]) { blk = mAvailBlockList[blk_idx] ; p = blk->allocate() ; if(blk->isFull()) { popAvailBlockList(blk_idx) ; } } //ask for a new block if(!p) { blk = addBlock(blk_idx) ; if(blk) { p = blk->allocate() ; if(blk->isFull()) { popAvailBlockList(blk_idx) ; } } } //ask for space from higher level blocks if(!p) { for(S32 i = blk_idx + 1 ; i < mBlockLevels; i++) { if(mAvailBlockList[i]) { blk = mAvailBlockList[i] ; p = blk->allocate() ; if(blk->isFull()) { popAvailBlockList(i) ; } break ; } } } llassert_always(!p || blk) ; if(p && blk) { if(blk->getTotalSlots() == 1) { llassert_always(blk->getBuffer() == (char*)p) ; } U32 blk_idx = getPageIndex((U32)p) ; LLMemoryBlock* b = (LLMemoryBlock*)(mMetaBuffer + blk_idx * sizeof(LLMemoryBlock)) ; llassert_always(blk == b || b->mSelf == blk) ; mAlloatedSize += blk->getSlotSize() ; } return p ; } void LLPrivateMemoryPool::LLMemoryChunk::free(void* addr) { U32 blk_idx = getPageIndex((U32)addr) ; LLMemoryBlock* blk = (LLMemoryBlock*)(mMetaBuffer + blk_idx * sizeof(LLMemoryBlock)) ; llassert_always(blk->mSelf) ; blk = blk->mSelf ; llassert_always(addr >= blk->getBuffer() && addr < blk->getBuffer() + blk->getBufferSize()) ; if(blk->getTotalSlots() == 1) { llassert_always(blk->getBuffer() == (char*)addr) ; } bool was_full = blk->isFull() ; blk->free(addr) ; mAlloatedSize -= blk->getSlotSize() ; if(blk->empty()) { blk->resetBitMap() ; //debug use removeBlock(blk) ; dump(); } else if(was_full) { addToAvailBlockList(blk) ; dump(); } } bool LLPrivateMemoryPool::LLMemoryChunk::empty() { return !mAlloatedSize ; } bool LLPrivateMemoryPool::LLMemoryChunk::containsAddress(const char* addr) const { return (U32)mBuffer <= (U32)addr && (U32)mBuffer + mBufferSize > (U32)addr ; } void LLPrivateMemoryPool::LLMemoryChunk::dump() { //sanity check std::vector< LLMemoryBlock* > blk_list ; for(std::set::iterator iter = mActiveBlockList.begin() ; iter != mActiveBlockList.end(); ++iter) { blk_list.push_back(*iter) ; } //for(S32 i = 0 ; i < mBlockLevels ; i++) //{ // LLMemoryBlock* blk = mAvailBlockList[i] ; // while(blk) // { // blk_list.push_back(blk) ; // blk = blk->mNext ; // } //} for(S32 i = 0 ; i < mPartitionLevels ; i++) { LLMemoryBlock* blk = mFreeSpaceList[i] ; while(blk) { blk_list.push_back(blk) ; blk = blk->mNext ; } } std::sort(blk_list.begin(), blk_list.end(), LLMemoryBlock::CompareAddress()); U32 total_size = blk_list[0]->getBufferSize() ; for(U32 i = 1 ; i < blk_list.size(); i++) { total_size += blk_list[i]->getBufferSize() ; if((U32)blk_list[i]->getBuffer() < (U32)blk_list[i-1]->getBuffer() + blk_list[i-1]->getBufferSize()) { llerrs << "buffer corrupted." << llendl ; } } llassert_always(total_size + mMinBlockSize >= mBufferSize - ((U32)mDataBuffer - (U32)mBuffer)) ; U32 blk_num = (mBufferSize - (mDataBuffer - mBuffer)) / mMinBlockSize ; for(U32 i = 0 ; i < blk_num ; ) { LLMemoryBlock* blk = &mBlocks[i] ; if(blk->mSelf) { U32 end = blk->getBufferSize() / mMinBlockSize ; for(U32 j = 0 ; j < end ; j++) { llassert_always(blk->mSelf == blk || !blk->mSelf) ; } i += end ; } else { llerrs << "gap happens" << llendl ; } } #if 0 llinfos << "---------------------------" << llendl ; llinfos << "Chunk buffer: " << (U32)getBuffer() << " size: " << getBufferSize() << llendl ; llinfos << "available blocks ... " << llendl ; for(S32 i = 0 ; i < mBlockLevels ; i++) { LLMemoryBlock* blk = mAvailBlockList[i] ; while(blk) { llinfos << "blk buffer " << (U32)blk->getBuffer() << " size: " << blk->getBufferSize() << llendl ; blk = blk->mNext ; } } llinfos << "free blocks ... " << llendl ; for(S32 i = 0 ; i < mPartitionLevels ; i++) { LLMemoryBlock* blk = mFreeSpaceList[i] ; while(blk) { llinfos << "blk buffer " << (U32)blk->getBuffer() << " size: " << blk->getBufferSize() << llendl ; blk = blk->mNext ; } } #endif } U32 LLPrivateMemoryPool::LLMemoryChunk::calcBlockSize(U32 slot_size) { // //Note: we try to make a block to have 32 slots if the size is not over 32 pages //32 is the number of bits of an integer in a 32-bit system // U32 block_size; U32 cut_off_size = llmin(CUT_OFF_SIZE, (U32)(mMinBlockSize << 5)) ; if((slot_size << 5) <= mMinBlockSize)//for small allocations, return one page { block_size = mMinBlockSize ; } else if(slot_size >= cut_off_size)//for large allocations, return one-slot block { block_size = (slot_size / mMinBlockSize) * mMinBlockSize ; if(block_size < slot_size) { block_size += mMinBlockSize ; } } else //medium allocations { if((slot_size << 5) >= cut_off_size) { block_size = cut_off_size ; } else { block_size = ((slot_size << 5) / mMinBlockSize) * mMinBlockSize ; } } llassert_always(block_size >= slot_size) ; return block_size ; } LLPrivateMemoryPool::LLMemoryBlock* LLPrivateMemoryPool::LLMemoryChunk::addBlock(U32 blk_idx) { U32 slot_size = mMinSlotSize * (blk_idx + 1) ; U32 preferred_block_size = calcBlockSize(slot_size) ; U16 idx = getPageLevel(preferred_block_size); llassert_always(idx < mPartitionLevels - 1) ; llassert_always(preferred_block_size == (idx + 1) * mMinBlockSize) ; //round to integer times of mMinBlockSize. LLMemoryBlock* blk = NULL ; if(mFreeSpaceList[idx])//if there is free slot for blk_idx { blk = createNewBlock(mFreeSpaceList[idx], preferred_block_size, slot_size, blk_idx) ; } else if(mFreeSpaceList[mPartitionLevels - 1]) //search free pool { blk = createNewBlock(mFreeSpaceList[mPartitionLevels - 1], preferred_block_size, slot_size, blk_idx) ; } else //search for other non-preferred but enough space slot. { for(S32 i = (S32)idx - 1 ; i >= 0 ; i--) //search the small slots first { if(mFreeSpaceList[i]) { U32 new_preferred_block_size = mFreeSpaceList[i]->getBufferSize(); new_preferred_block_size = (new_preferred_block_size / mMinBlockSize) * mMinBlockSize ; //round to integer times of mMinBlockSize. //create a NEW BLOCK THERE. if(new_preferred_block_size >= slot_size) //at least there is space for one slot. { blk = createNewBlock(mFreeSpaceList[i], new_preferred_block_size, slot_size, blk_idx) ; } break ; } } if(!blk) { for(U16 i = idx + 1 ; i < mPartitionLevels - 1; i++) //search the large slots { if(mFreeSpaceList[i]) { //create a NEW BLOCK THERE. blk = createNewBlock(mFreeSpaceList[i], preferred_block_size, slot_size, blk_idx) ; break ; } } } } dump() ; return blk ; } char* _prev = NULL ; LLPrivateMemoryPool::LLMemoryBlock* LLPrivateMemoryPool::LLMemoryChunk::createNewBlock(LLMemoryBlock* blk, U32 buffer_size, U32 slot_size, U32 blk_idx) { llassert_always(blk->getBufferSize() >= buffer_size) ; //debug { { U32 blk_idx = getPageIndex((U32)blk->getBuffer()) ; llassert_always(blk == (LLMemoryBlock*)(mMetaBuffer + blk_idx * sizeof(LLMemoryBlock))) ; } U32 end = (blk->getBufferSize() / mMinBlockSize) ; llassert_always(blk->mSelf == blk && blk->isFree()) ; llassert_always((blk + end - 1)->mSelf == blk) ; for(U32 i = 1 ; i < end - 1; i++) { llassert_always(!(blk + i)->mSelf) ; } } //unlink from the free space removeFromFreeSpace(blk) ; //check the rest space U32 new_free_blk_size = blk->getBufferSize() - buffer_size ; if(new_free_blk_size < mMinBlockSize) //can not partition the memory into size smaller than mMinBlockSize { new_free_blk_size = 0 ; //discard the last small extra space. } //add the rest space back to the free list if(new_free_blk_size > 0) //blk still has free space { LLMemoryBlock* next_blk = blk + (buffer_size / mMinBlockSize) ; next_blk->setBuffer(blk->getBuffer() + buffer_size, new_free_blk_size) ; { U32 blk_idx = getPageIndex((U32)next_blk->getBuffer()) ; llassert_always(next_blk == (LLMemoryBlock*)(mMetaBuffer + blk_idx * sizeof(LLMemoryBlock))) ; } llassert_always(buffer_size == (buffer_size / mMinBlockSize) * mMinBlockSize) ; llassert_always(((U32)next_blk->getBuffer() - (U32)mDataBuffer) == ((U32)next_blk->getBuffer() - (U32)mDataBuffer) / mMinBlockSize * mMinBlockSize) ; addToFreeSpace(next_blk) ; } blk->init(blk->getBuffer(), buffer_size, slot_size) ; //insert to the available block list... llassert_always(!mAvailBlockList[blk_idx]) ; mAvailBlockList[blk_idx] = blk ; llassert_always(blk->getTotalSlots() > 0) ; llassert_always(mAvailBlockList[blk_idx]->getSlotSize() == (blk_idx + 1) * mMinSlotSize) ; llassert_always(buffer_size == (buffer_size / mMinBlockSize) * mMinBlockSize) ; //mark the address map U32 end = (buffer_size / mMinBlockSize) ; for(U32 i = 1 ; i < end ; i++) { (blk + i)->mSelf = blk ; } llassert_always(blk->getBuffer() != _prev) ; llassert_always(mActiveBlockList.find(blk) == mActiveBlockList.end()) ; mActiveBlockList.insert(blk) ; return blk ; } void LLPrivateMemoryPool::LLMemoryChunk::removeBlock(LLMemoryBlock* blk) { //remove from the available block list if(blk->mPrev) { blk->mPrev->mNext = blk->mNext ; } if(blk->mNext) { blk->mNext->mPrev = blk->mPrev ; } U32 blk_idx = getBlockLevel(blk->getSlotSize()); if(mAvailBlockList[blk_idx] == blk) { mAvailBlockList[blk_idx] = blk->mNext ; } blk->mNext = NULL ; blk->mPrev = NULL ; std::set::iterator iter = mActiveBlockList.find(blk) ; llassert_always(iter != mActiveBlockList.end()) ; mActiveBlockList.erase(iter) ; //mark it free blk->setBuffer(blk->getBuffer(), blk->getBufferSize()) ; //debug U32 end = (blk->getBufferSize() / mMinBlockSize) ; for(U32 i = 1 ; i < end ; i++) { llassert_always((blk + i)->mSelf == blk) ; (blk + i)->mSelf = NULL ; } #if 0 //merge blk with neighbors if possible if(blk->getBuffer() > mDataBuffer) //has the left neighbor { if((blk - 1)->mSelf->isFree()) { LLMemoryBlock* left_blk = (blk - 1)->mSelf ; removeFromFreeSpace((blk - 1)->mSelf); left_blk->setBuffer(left_blk->getBuffer(), left_blk->getBufferSize() + blk->getBufferSize()) ; blk = left_blk ; } } if(blk->getBuffer() + blk->getBufferSize() <= mBuffer + mBufferSize - mMinBlockSize) //has the right neighbor { U32 d = blk->getBufferSize() / mMinBlockSize ; if((blk + d)->isFree()) { LLMemoryBlock* right_blk = blk + d ; removeFromFreeSpace(blk + d) ; blk->setBuffer(blk->getBuffer(), blk->getBufferSize() + right_blk->getBufferSize()) ; } } #endif llassert_always(blk->getBuffer() != _prev) ; llassert_always(mActiveBlockList.find(blk) == mActiveBlockList.end()) ; addToFreeSpace(blk) ; return ; } //the top block in the list is full, pop it out of the list void LLPrivateMemoryPool::LLMemoryChunk::popAvailBlockList(U32 blk_idx) { if(mAvailBlockList[blk_idx]) { LLMemoryBlock* next = mAvailBlockList[blk_idx]->mNext ; if(next) { next->mPrev = NULL ; } mAvailBlockList[blk_idx]->mPrev = NULL ; mAvailBlockList[blk_idx]->mNext = NULL ; mAvailBlockList[blk_idx] = next ; if(next) { llassert_always(mAvailBlockList[blk_idx]->getTotalSlots() > 0) ; llassert_always(mAvailBlockList[blk_idx]->getSlotSize() == (blk_idx + 1) * mMinSlotSize) ; } dump() ; } } void LLPrivateMemoryPool::LLMemoryChunk::addToFreeSpace(LLMemoryBlock* blk) { llassert_always(!blk->mPrev) ; llassert_always(!blk->mNext) ; U16 free_idx = blk->getBufferSize() / mMinBlockSize - 1; (blk + free_idx)->mSelf = blk ; //mark the end pointing back to the head. free_idx = llmin(free_idx, (U16)(mPartitionLevels - 1)) ; blk->mNext = mFreeSpaceList[free_idx] ; if(mFreeSpaceList[free_idx]) { mFreeSpaceList[free_idx]->mPrev = blk ; } mFreeSpaceList[free_idx] = blk ; blk->mPrev = NULL ; blk->mSelf = blk ; return ; } void LLPrivateMemoryPool::LLMemoryChunk::removeFromFreeSpace(LLMemoryBlock* blk) { U16 free_idx = blk->getBufferSize() / mMinBlockSize - 1; free_idx = llmin(free_idx, (U16)(mPartitionLevels - 1)) ; if(mFreeSpaceList[free_idx] == blk) { mFreeSpaceList[free_idx] = blk->mNext ; } if(blk->mPrev) { blk->mPrev->mNext = blk->mNext ; } if(blk->mNext) { blk->mNext->mPrev = blk->mPrev ; } blk->mNext = NULL ; blk->mPrev = NULL ; blk->mSelf = NULL ; return ; } void LLPrivateMemoryPool::LLMemoryChunk::addToAvailBlockList(LLMemoryBlock* blk) { llassert_always(!blk->mPrev) ; llassert_always(!blk->mNext) ; U32 blk_idx = getBlockLevel(blk->getSlotSize()); llassert_always(blk->getSlotSize() == (blk_idx + 1) * mMinSlotSize) ; blk->mNext = mAvailBlockList[blk_idx] ; if(blk->mNext) { blk->mNext->mPrev = blk ; } blk->mPrev = NULL ; mAvailBlockList[blk_idx] = blk ; llassert_always(mAvailBlockList[blk_idx]->getTotalSlots() > 0) ; llassert_always(mAvailBlockList[blk_idx]->getSlotSize() == (blk_idx + 1) * mMinSlotSize) ; return ; } U32 LLPrivateMemoryPool::LLMemoryChunk::getPageIndex(U32 addr) { return (addr - (U32)mDataBuffer) / mMinBlockSize ; } //for mAvailBlockList U32 LLPrivateMemoryPool::LLMemoryChunk::getBlockLevel(U32 size) { llassert(size >= mMinSlotSize && size <= mMaxSlotSize) ; //start from 0 return (size + mMinSlotSize - 1) / mMinSlotSize - 1 ; } //for mFreeSpaceList U16 LLPrivateMemoryPool::LLMemoryChunk::getPageLevel(U32 size) { llassert_always(size >= mMinBlockSize); llassert_always(!(size % mMinBlockSize)) ; //start from 0 U16 level = size / mMinBlockSize - 1 ; if(level >= mPartitionLevels) { level = mPartitionLevels - 1 ; } return level ; } //------------------------------------------------------------------- //class LLPrivateMemoryPool //-------------------------------------------------------------------- const U32 CHUNK_SIZE = 4 << 20 ; //4 MB const U32 HASH_FACTOR = 255 ; LLPrivateMemoryPool::LLPrivateMemoryPool(U32 max_size, bool threaded) : mMutexp(NULL), mMaxPoolSize(max_size), mReservedPoolSize(0) { if(threaded) { mMutexp = new LLMutex(NULL) ; } for(S32 i = 0 ; i < SUPER_ALLOCATION ; i++) { mChunkList[i] = NULL ; } mChunkHashList.resize(HASH_FACTOR + 1) ; for(U32 i = 0 ; i <= HASH_FACTOR ; i++) { mChunkHashList[i] = NULL ; } mNumOfChunks = 0 ; } LLPrivateMemoryPool::~LLPrivateMemoryPool() { destroyPool(); delete mMutexp ; } char* LLPrivateMemoryPool::allocate(U32 size) { if(!size) { return NULL ; } //if the asked size larger than MAX_BLOCK_SIZE, fetch from heap directly, the pool does not manage it if(size >= CHUNK_SIZE) { return new char[size] ; } char* p = NULL ; //find the appropriate chunk S32 chunk_idx = getChunkIndex(size) ; lock() ; LLMemoryChunk* chunk = mChunkList[chunk_idx]; while(chunk) { if(p = chunk->allocate(size)) { break ; } chunk = chunk->mNext ; } //fetch new memory chunk if(!p) { if(mReservedPoolSize + CHUNK_SIZE > mMaxPoolSize) { chunk = mChunkList[chunk_idx]; while(chunk) { if(p = chunk->allocate(size)) { break ; } chunk = chunk->mNext ; } } chunk = addChunk(chunk_idx) ; if(chunk) { p = chunk->allocate(size) ; } } unlock() ; return p ; } void LLPrivateMemoryPool::free(void* addr) { if(!addr) { return ; } lock() ; U16 key ; LLMemoryChunk* chunk =findChunk((char*)addr, key) ; if(!chunk) { delete[] (char*)addr ; //release from heap } else { llassert_always((U32)addr >= (U32)chunk->getBuffer() && (U32)addr < (U32)chunk->getBuffer() + chunk->getBufferSize()) ; chunk->free(addr) ; if(chunk->empty()) { removeChunk(chunk, key) ; } } unlock() ; } LLPrivateMemoryPool::LLMemoryChunk* LLPrivateMemoryPool::findChunk(const char* addr, U16& key) { key = findHashKey(addr) ; //check the hash value "key" LLMemoryChunk* chunk = mChunkHashList[key] ; while(chunk && !chunk->containsAddress(addr)) { chunk = chunk->mHashNext ; } if(!chunk && key > 0) //check the "key - 1" { chunk = mChunkHashList[key - 1] ; while(chunk && !chunk->containsAddress(addr)) { chunk = chunk->mHashNext ; } if(chunk) { key-- ; } } if(!chunk && key < HASH_FACTOR) //check the "key + 1" { chunk = mChunkHashList[key + 1] ; while(chunk && !chunk->containsAddress(addr)) { chunk = chunk->mHashNext ; } if(chunk) { key++ ; } } return chunk ; } void LLPrivateMemoryPool::dump() { } U32 LLPrivateMemoryPool::getTotalAllocatedSize() { U32 total_allocated = 0 ; LLMemoryChunk* chunk ; for(S32 i = 0 ; i < SUPER_ALLOCATION ; i++) { chunk = mChunkList[i]; while(chunk) { total_allocated += chunk->getAllocatedSize() ; chunk = chunk->mNext ; } } return total_allocated ; } void LLPrivateMemoryPool::lock() { if(mMutexp) { mMutexp->lock() ; } } void LLPrivateMemoryPool::unlock() { if(mMutexp) { mMutexp->unlock() ; } } S32 LLPrivateMemoryPool::getChunkIndex(U32 size) { S32 i ; for(i = 0 ; size > MAX_SLOT_SIZES[i]; i++); llassert_always(i < SUPER_ALLOCATION); return i ; } //destroy the entire pool void LLPrivateMemoryPool::destroyPool() { lock() ; for(U32 i = 0 ; i <= HASH_FACTOR; i++) { while(mChunkHashList[i]) { removeChunk(mChunkHashList[i], i) ; } } llassert_always(mNumOfChunks == 0) ; llassert_always(mReservedPoolSize == 0) ; for(S32 i = 0 ; i < SUPER_ALLOCATION ; i++) { mChunkList[i] = NULL ; } unlock() ; } void LLPrivateMemoryPool::checkSize(U32 asked_size) { if(mReservedPoolSize + asked_size > mMaxPoolSize) { llinfos << "Max pool size: " << mMaxPoolSize << llendl ; llinfos << "Total reserved size: " << mReservedPoolSize + asked_size << llendl ; llinfos << "Total_allocated Size: " << getTotalAllocatedSize() << llendl ; llerrs << "The pool is overflowing..." << llendl ; } } LLPrivateMemoryPool::LLMemoryChunk* LLPrivateMemoryPool::addChunk(S32 chunk_index) { U32 preferred_size ; U32 overhead ; if(chunk_index < LARGE_ALLOCATION) { preferred_size = CHUNK_SIZE ; //4MB overhead = LLMemoryChunk::getMaxOverhead(preferred_size, MIN_BLOCK_SIZES[chunk_index]) ; } else { preferred_size = 4 * CHUNK_SIZE ; //16MB overhead = LLMemoryChunk::getMaxOverhead(preferred_size, MIN_BLOCK_SIZES[chunk_index]) ; } checkSize(preferred_size + overhead) ; mReservedPoolSize += preferred_size + overhead ; char* buffer = new(std::nothrow) char[preferred_size + overhead] ; if(!buffer) { return NULL ; } memset(buffer, 0, preferred_size + overhead) ; LLMemoryChunk* chunk = new (buffer) LLMemoryChunk() ; chunk->init(buffer, preferred_size + overhead, MIN_SLOT_SIZES[chunk_index], MAX_SLOT_SIZES[chunk_index], MIN_BLOCK_SIZES[chunk_index], MAX_BLOCK_SIZES[chunk_index]) ; //add to the head of the linked list chunk->mNext = mChunkList[chunk_index] ; if(mChunkList[chunk_index]) { mChunkList[chunk_index]->mPrev = chunk ; } chunk->mPrev = NULL ; mChunkList[chunk_index] = chunk ; //insert into the hash table U16 key = findHashKey(chunk->getBuffer()) ; chunk->mHashNext = mChunkHashList[key] ; mChunkHashList[key] = chunk ; mNumOfChunks++; return chunk ; } char*** _p = NULL ; U32 _times; U32 _levels; void LLPrivateMemoryPool::removeChunk(LLMemoryChunk* chunk, U16 key) { if(!chunk) { return ; } //remove from the linked list for(S32 i = 0 ; i < SUPER_ALLOCATION ; i++) { if(mChunkList[i] == chunk) { mChunkList[i] = chunk->mNext ; } } if(chunk->mPrev) { chunk->mPrev->mNext = chunk->mNext ; } if(chunk->mNext) { chunk->mNext->mPrev = chunk->mPrev ; } //remove from the hash table if(mChunkHashList[key] == chunk) { mChunkHashList[key] = chunk->mHashNext ; } else { LLMemoryChunk* prev = mChunkHashList[key] ; while(prev->mHashNext && prev->mHashNext != chunk) { prev = prev->mHashNext ; } llassert_always(prev->mHashNext == chunk) ; prev->mHashNext = chunk->mHashNext ; } mNumOfChunks--; mReservedPoolSize -= chunk->getBufferSize() ; //debug check if(_p) { for(U32 i = 0 ; i < _times; i++) { for(U32 j = 0 ; j < _levels ;j++) { if( i == col && j == row) { continue ; } llassert_always(!_p[i][j] || !chunk->containsAddress(_p[i][j])) ; } } } //release memory delete[] chunk->getBuffer() ; } U16 LLPrivateMemoryPool::findHashKey(const char* addr) { return (((U32)addr) / CHUNK_SIZE) % HASH_FACTOR ; } //-------------------------------------------------------------------- //class LLPrivateMemoryPoolTester LLPrivateMemoryPoolTester* LLPrivateMemoryPoolTester::sInstance = NULL ; LLPrivateMemoryPool* LLPrivateMemoryPoolTester::sPool = NULL ; LLPrivateMemoryPoolTester::LLPrivateMemoryPoolTester() { } LLPrivateMemoryPoolTester::~LLPrivateMemoryPoolTester() { } //static LLPrivateMemoryPoolTester* LLPrivateMemoryPoolTester::getInstance() { if(!sInstance) { sInstance = ::new LLPrivateMemoryPoolTester() ; } return sInstance ; } //static void LLPrivateMemoryPoolTester::destroy() { if(sInstance) { ::delete sInstance ; sInstance = NULL ; } if(sPool) { ::delete sPool ; sPool = NULL ; } } void LLPrivateMemoryPoolTester::run(bool threaded) { const U32 max_pool_size = 1024 << 20 ; if(sPool) { ::delete sPool ; } sPool = ::new LLPrivateMemoryPool(max_pool_size, threaded) ; //run the test correctnessTest() ; //performanceTest() ; //fragmentationtest() ; //release pool. ::delete sPool ; sPool = NULL ; } void LLPrivateMemoryPoolTester::test(U32 min_size, U32 max_size, U32 stride, U32 times, bool random_deletion, bool output_statistics) { U32 levels = (max_size - min_size) / stride + 1 ; char*** p ; U32 i, j ; U32 total_allocated_size = 0 ; //allocate space for p ; if(!(p = ::new char**[times]) || !(*p = ::new char*[times * levels])) { llerrs << "memory initialization for p failed" << llendl ; } //init for(i = 0 ; i < times; i++) { p[i] = *p + i * levels ; for(j = 0 ; j < levels; j++) { p[i][j] = NULL ; } } _p = p ; _times = times; _levels = levels ; //allocation U32 size ; for(i = 0 ; i < times ; i++) { for(j = 0 ; j < levels; j++) { size = min_size + j * stride ; _prev = p[i][j] = sPool->allocate(size) ; total_allocated_size+= size ; *(U32*)p[i][j] = i ; *((U32*)p[i][j] + 1) = j ; //p[i][j][size - 1] = '\0' ; //access the last element to verify the success of the allocation. //randomly release memory if(random_deletion) { S32 k = rand() % levels ; col = i ; row = k ; if(p[i][k]) { if(_prev == p[i][k]) { _prev = NULL ; } llassert_always(*(U32*)p[i][k] == i && *((U32*)p[i][k] + 1) == k) ; sPool->free(p[i][k]) ; total_allocated_size -= min_size + k * stride ; p[i][k] = NULL ; } } } } //output pool allocation statistics if(output_statistics) { } _prev = NULL ; //release all memory allocations for(i = 0 ; i < times; i++) { for(j = 0 ; j < levels; j++) { col = i ; row = j ; if(p[i][j]) { llassert_always(*(U32*)p[i][j] == i && *((U32*)p[i][j] + 1) == j) ; sPool->free(p[i][j]) ; total_allocated_size -= min_size + j * stride ; p[i][j] = NULL ; } } } ::delete[] *p ; ::delete[] p ; _p = NULL ; } void LLPrivateMemoryPoolTester::correctnessTest() { //try many different sized allocation, and all kinds of edge cases, access the allocated memory //to see if allocation is right. //edge case char* p = sPool->allocate(0) ; sPool->free(p) ; //small sized // [8 bytes, 2KB), each asks for 256 allocations and deallocations test(8, 2040, 8, 256, true, true) ; //medium sized //[2KB, 512KB), each asks for 16 allocations and deallocations test(2048, 512 * 1024 - 2048, 2048, 16, true, true) ; //large sized //[512KB, 4MB], each asks for 8 allocations and deallocations test(512 * 1024, 4 * 1024 * 1024, 64 * 1024, 6, true, true) ; } void LLPrivateMemoryPoolTester::performanceTest() { U32 test_size[3] = {768, 3* 1024, 3* 1024 * 1024}; S32 i ; LLFrameTimer timer ; //do 1024 various-sized allocations / deallocations, compare the performance with the normal ones. //small sized { timer.reset() ; char* p[1024] = {NULL} ; for(i = 0 ; i < 1024; i++) { p[i] = sPool->allocate(test_size[0]) ; if(!p[i]) { llerrs << "allocation failed" << llendl ; } } for(i = 0 ; i < 1024; i++) { sPool->free(p[i]) ; p[i] = NULL ; } llinfos << "time spent on 1024 small allocations: %f " << timer.getElapsedTimeF32() << llendl ; timer.reset() ; //using the standard allocator/de-allocator: for(i = 0 ; i < 1024; i++) { p[i] = ::new char[test_size[0]] ; if(!p[i]) { llerrs << "allocation failed" << llendl ; } } for(i = 0 ; i < 1024; i++) { ::delete[] p[i] ; p[i] = NULL ; } llinfos << "time spent on 1024 small allocations: %f using standard allocator/de-allocator." << timer.getElapsedTimeF32() << llendl ; timer.reset() ; } //medium sized //large sized } void LLPrivateMemoryPoolTester::fragmentationtest() { //for internal fragmentation statistics: //every time when asking for a new chunk during correctness test, and performance test, //print out the chunk usage statistices. } //--------------------------------------------------------------------