Files
FC1/CrySystem/CryMemoryManager.cpp
romkazvo 34d6c5d489 123
2023-08-07 19:29:24 +08:00

499 lines
13 KiB
C++

// CryMemoryManager.cpp : Defines the entry point for the DLL application.
#include "stdafx.h"
#include <stdlib.h>
#include <stdio.h>
#include <new.h>
#include <ISystem.h>
#include "platform.h"
#undef USE_NEWPOOL
#include <CryMemoryManager.h>
//////////////////////////////////////////////////////////////////////////
// Some globals for fast profiling.
//////////////////////////////////////////////////////////////////////////
size_t g_TotalAllocatedMemory = 0;
size_t g_ScriptAllocatedMemory = 0;
int g_nPrecaution = 0; // will cause delayed crash, will make engine extremally unstable.
//#if !defined(LINUX)
extern ISystem* g_System;
extern bool g_bProfilerEnabled;
//////////////////////////////////////////////////////////////////////////
// Undefine malloc for memory manager itself..
#undef malloc
#undef realloc
#undef free
#ifndef _XBOX
#define GLOBALPOOLSIZE (32*1025*1025+16)
#define EXTRAPOOLSIZE (32*1025*1025+16) // how much to allocate if we allow the pool to be exceeded
// currently biggest allocation happends during hires screenshot creation
#else // _XBOX
#ifndef _DEBUG
#define GLOBALPOOLSIZE (8*1025*1025+16)
#define EXTRAPOOLSIZE (8*1025*1025+16)
#else // _DEBUG
#define GLOBALPOOLSIZE (0)
#define EXTRAPOOLSIZE (0)
#endif // _DEBUG
#endif // _XBOX
#define MAXPOOLS 128
void *poolbufs[MAXPOOLS];
int poolsizes[MAXPOOLS];
int numpools = 0;
#if defined(WIN32) || defined(LINUX)
#if defined(WIN64) || defined(LINUX64)
// non-512 doesn't work for some reason: an infinite loop (recursion with stack overflow) occurs
#define BUCKETQUANT 512 // wouter: affects performance of bucket allocator, modify with care!
#else
#define BUCKETQUANT 512*2
#endif
#else
#define BUCKETQUANT 256 // save some memory overhead with tiny speed cost on consoles
#endif
/*
PoolContext *AllocPool(PoolContext *pCtx, int size)
{
// TODO: replace "malloc" with however we obtain memory on other platforms
void *buf = VirtualAlloc(NULL,size,MEM_COMMIT,PAGE_READWRITE);
if(!buf)
{
CryError( "<CrySystem> (AllocPool) malloc() Failed" );
};
bpool(pCtx, buf, size);
if (numpools==MAXPOOLS)
{
CryError( "<CrySystem> (AllocPool) Maximum number of memory pools reached." );
}
poolsizes[numpools] = size;
poolbufs[numpools++] = buf;
return pCtx;
};
*/
//static PoolContext globalpool;
//static PoolContext *g_pool = AllocPool(InitPoolContext(&globalpool), GLOBALPOOLSIZE);
static unsigned int biggestalloc = 0;
/*
#define MAXSTAT 1000
static int stats[MAXSTAT];
void addstat(int size) { if(size<0 || size>=MAXSTAT) size = MAXSTAT-1; stats[size]++; };
void printstats() { for(int i = 0; i<MAXSTAT; i++) if(stats[i]) { char buf[100]; sprintf(buf, "bucket %d -> %d\n", i, stats[i]); ::OutputDebugString(buf); }; };
int clearstats() { for(int i = 0; i<MAXSTAT; i++) stats[i] = 0; return 0; };
static int foo = clearstats();
*/
class PageBucketAllocator
{
/*
Generic allocator that combines bucket allocation with reference counted 1 size object pages.
manages to perform well along each axis:
- very fast for small objects: only a few instructions in the general case for alloc/dealloc,
up to several orders of magnitude faster than traditional best fit allocators
- low per object memory overhead: 0 bytes overhead on small objects, small overhead for
pages that are partially in use (still significantly lower than other allocators).
- almost no fragmentation, reuse of pages is close to optimal
- very good cache locality (page aligned, same size objects)
*/
enum { PAGESIZE = 4096 };
enum { PAGEMASK = (~(PAGESIZE-1)) };
//enum { PAGESATONCE = 64 };
enum { PAGESATONCE = 32 };
enum { PAGEBLOCKSIZE = PAGESIZE*PAGESATONCE };
enum { PTRSIZE = sizeof(char *) };
enum { MAXBUCKETS = BUCKETQUANT/4+1 }; // meaning up to size 512 on 32bit pointer systems
enum { MAXREUSESIZE = MAXBUCKETS*PTRSIZE-PTRSIZE };
int bucket(int s) { return (s+PTRSIZE-1)>>PTRBITS; };
int *ppage(void *p) { return (int *)(((INT_PTR)p)&PAGEMASK); };
enum { PTRBITS = PTRSIZE==2 ? 1 : PTRSIZE==4 ? 2 : 3 };
void *reuse[MAXBUCKETS];
void **pages;
//! Total allocated size.
void putinbuckets(char *start, char *end, int bsize)
{
int size = bsize*PTRSIZE;
for(end -= size; start<=end; start += size)
{
*((void **)start) = reuse[bsize];
reuse[bsize] = start;
};
};
void newpageblocks()
{
char *b = (char *)::malloc(PAGEBLOCKSIZE); // if we could get page aligned memory here, that would be even better
char *first = ((char *)ppage(b))+PAGESIZE;
for(int i = 0; i<PAGESATONCE-1; i++)
{
void **p = (void **)(first+i*PAGESIZE);
*p = pages;
pages = p;
};
//if(b-first+PAGESIZE>BUCKETQUANT) bpool(g_pool, first+PAGEBLOCKSIZE-PAGESIZE, b-first+PAGESIZE);
};
void *newpage(unsigned int bsize)
{
if(!pages) newpageblocks();
void **page = pages;
pages = (void **)*pages;
*page = 0;
putinbuckets((char *)(page+1), ((char *)page)+PAGESIZE, bsize);
return alloc(bsize*PTRSIZE);
};
void freepage(int *page, int bsize) // worst case if very large amounts of objects get deallocated in random order from when they were allocated
{
for(void **r = &reuse[bsize]; *r; )
{
if(page == ppage(*r)) *r = *((void **)*r);
else r = (void **)*r;
};
void **p = (void **)page;
*p = pages;
pages = p;
};
public:
PageBucketAllocator()
{
pages = NULL;
for(int i = 0; i<MAXBUCKETS; i++) reuse[i] = NULL;
};
void *alloc(unsigned int size)
{
if(size>biggestalloc)
{
biggestalloc = size;
};
if(size>MAXREUSESIZE) return ::malloc(size);
size = bucket(size);
void **r = (void **)reuse[size];
if(!r) return newpage(size);
reuse[size] = *r;
int *page = ppage(r);
(*page)++;
return (void *)r;
};
void dealloc(void *p, unsigned int size)
{
if(size>MAXREUSESIZE)
{
::free(p);
//brel(g_pool, p);
}
else
{
size = bucket(size);
*((void **)p) = reuse[size];
reuse[size] = p;
int *page = ppage(p);
if(!--(*page)) freepage(page, size);
};
};
void stats()
{
int totalwaste = 0;
char buf[100];
for(int i = 0; i<MAXBUCKETS; i++)
{
int n = 0;
for(void **r = (void **)reuse[i]; r; r = (void **)*r) n++;
if(n)
{
int waste = i*4*n/1024;
totalwaste += waste;
sprintf(buf, "bucket %d -> %d (%d k)\n", i*4, n, waste);
::OutputDebugString(buf);
};
};
sprintf(buf, "totalwaste %d k\n", totalwaste);
::OutputDebugString(buf);
};
};
PageBucketAllocator g_GlobPageBucketAllocator;
CRYMEMORYMANAGER_API void *CryMalloc(size_t size)
{
if (!g_bProfilerEnabled)
{
g_TotalAllocatedMemory += size+sizeof(int);
int *p = (int *)g_GlobPageBucketAllocator.alloc(size+sizeof(int));
*p++ = size; // stores 2 sizes for big objects!
return p;
}
else
{
FUNCTION_PROFILER_FAST( g_System,PROFILE_SYSTEM,g_bProfilerEnabled );
g_TotalAllocatedMemory += size+sizeof(int);
int *p = (int *)g_GlobPageBucketAllocator.alloc(size+sizeof(int));
*p++ = size; // stores 2 sizes for big objects!
return p;
}
}
CRYMEMORYMANAGER_API void CryFree(void *p)
{
if (!g_bProfilerEnabled)
{
if (p != NULL)
{
unsigned int *t = (unsigned int *)p;
unsigned int size = *--t;
#ifdef MINIMALDEBUG
if (size>=100000000)
{
CryError( "\001[CRYMANAGER ERROR](CryFree): illegal size 0x%X - block header was corrupted",size);
}
#endif
size += sizeof(int);
#ifdef GARBAGEMEMORY //FIXME: *disabling* memset caused random crash???
memset(t, 0xBA, size);
#endif
g_TotalAllocatedMemory -= size;
g_GlobPageBucketAllocator.dealloc(t, size);
}
}
else
{
// With profiler.
if (p != NULL)
{
FUNCTION_PROFILER_FAST( g_System,PROFILE_SYSTEM,g_bProfilerEnabled );
unsigned int *t = (unsigned int *)p;
unsigned int size = *--t;
#ifdef MINIMALDEBUG
if (size>=100000000)
{
CryError( "\001[CRYMANAGER ERROR](CryFree): illegal size 0x%X - block header was corrupted",size);
}
#endif
size += sizeof(int);
#ifdef GARBAGEMEMORY //FIXME: *disabling* memset caused random crash???
memset(t, 0xBA, size);
#endif
g_TotalAllocatedMemory -= size;
g_GlobPageBucketAllocator.dealloc(t, size);
}
}
}
CRYMEMORYMANAGER_API void CryFreeSize(void *p, size_t size)
{
g_ScriptAllocatedMemory -= size;
g_TotalAllocatedMemory -= size;
if (!g_bProfilerEnabled)
{
if (p != NULL)
{
#ifdef MINIMALDEBUG
if (size>=100000000)
{
CryError("[CRYMANAGER ERROR](CryFreeSize): illegal size 0x%X - block header was corrupted",size );
}
#endif
#ifdef GARBAGEMEMORY //FIXME: idem
memset(p, 0xBB, size);
#endif
g_GlobPageBucketAllocator.dealloc(p, size);
}
}
else
{
// With profiler.
if (p != NULL)
{
FUNCTION_PROFILER_FAST( g_System,PROFILE_SYSTEM,g_bProfilerEnabled );
#ifdef MINIMALDEBUG
if (size>=100000000)
{
CryError("[CRYMANAGER ERROR](CryFreeSize): illegal size 0x%X - block header was corrupted",size );
}
#endif
#ifdef GARBAGEMEMORY //FIXME: idem
memset(p, 0xBB, size);
#endif
g_GlobPageBucketAllocator.dealloc(p, size);
}
}
}
CRYMEMORYMANAGER_API void *CryRealloc(void *memblock, size_t size)
{
if (!g_bProfilerEnabled)
{
// Without profiler.
if(memblock==NULL)
return CryMalloc(size);
else
{
void *np = CryMalloc(size);
size_t oldsize = ((int *)memblock)[-1];
memcpy(np, memblock, size>oldsize ? oldsize : size);
CryFree(memblock);
return np;
}
}
else
{
// With Profiler.
FUNCTION_PROFILER_FAST( g_System,PROFILE_SYSTEM,g_bProfilerEnabled );
if(memblock==NULL)
return CryMalloc(size);
else
{
void *np = CryMalloc(size);
size_t oldsize = ((int *)memblock)[-1];
memcpy(np, memblock, size>oldsize ? oldsize : size);
CryFree(memblock);
return np;
}
}
}
CRYMEMORYMANAGER_API void *CryReallocSize(void *memblock, size_t oldsize, size_t size)
{
g_ScriptAllocatedMemory += size; // -old size done in CryFreeSize
g_TotalAllocatedMemory += size;
if (!g_bProfilerEnabled)
{
if(memblock==NULL)
{
return (char*)g_GlobPageBucketAllocator.alloc(size) + g_nPrecaution;
}
else
{
void *np = (char*)g_GlobPageBucketAllocator.alloc(size) + g_nPrecaution;
memcpy(np, memblock, size>oldsize ? oldsize : size);
CryFreeSize(memblock, oldsize);
return np;
}
}
else
{
FUNCTION_PROFILER_FAST( g_System,PROFILE_SYSTEM,g_bProfilerEnabled );
if(memblock==NULL)
{
return (char*)g_GlobPageBucketAllocator.alloc(size) + g_nPrecaution;
}
else
{
void *np = (char*)g_GlobPageBucketAllocator.alloc(size) + g_nPrecaution;
memcpy(np, memblock, size>oldsize ? oldsize : size);
CryFreeSize(memblock, oldsize);
return np;
}
}
}
CRYMEMORYMANAGER_API void CryFlushAll() // releases/resets ALL memory... this is useful for restarting the game
{
/*
InitPoolContext(g_pool);
for(int i = 0; i<numpools; i++)
bpool(g_pool, poolbufs[i], poolsizes[i]);
*/
new (&g_GlobPageBucketAllocator) PageBucketAllocator();
g_ScriptAllocatedMemory = 0;
g_TotalAllocatedMemory = 0;
};
/* MarcoK: This is never used anywhere ... commented out (LINUX port)
extern "C" CRYMEMORYMANAGER_API void CryFreeMemoryPools() // releases/resets ALL memory... this is useful for restarting the game
{
for(int i = 0; i<numpools; i++)
{
void *pBuf = poolbufs[i];
VirtualFree( pBuf,0,MEM_RELEASE );
}
numpools = 0;
g_ScriptAllocatedMemory = 0;
g_TotalAllocatedMemory = 0;
};
*/
//////////////////////////////////////////////////////////////////////////
// Returns ammount of memory allocated with CryMalloc/CryFree functions.
//////////////////////////////////////////////////////////////////////////
CRYMEMORYMANAGER_API int CryMemoryGetAllocatedSize()
{
return g_TotalAllocatedMemory;
}
//////////////////////////////////////////////////////////////////////////
CRYMEMORYMANAGER_API int CryMemoryGetAllocatedInScriptSize()
{
return g_ScriptAllocatedMemory;
}
//////////////////////////////////////////////////////////////////////////
CRYMEMORYMANAGER_API int CryMemoryGetPoolSize()
{
int totalsize = 0;
for(int i = 0; i<numpools; i++)
totalsize += poolsizes[i];
return totalsize;
}
//////////////////////////////////////////////////////////////////////////
CRYMEMORYMANAGER_API int CryStats(char *buf)
{
long curalloc=0, totfree=0, maxfree=0, nget=0, nrel=0;
//bstats(g_pool, &curalloc, &totfree, &maxfree, &nget, &nrel);
if(buf)
{
int poolsize = CryMemoryGetPoolSize();
int scriptalloc = CryMemoryGetAllocatedInScriptSize();
sprintf(buf, "Memory Allocated = %d K, totfree = %d K , maxfree = %d K, nmalloc = %d, nfree = %d, biggestalloc = %d, Pool Size = %d K, Lua Allocated = %d K",
curalloc/1024, totfree/1024, maxfree/1024, nget, nrel, biggestalloc,poolsize/1024,scriptalloc/1024);
//printstats();
g_GlobPageBucketAllocator.stats();
};
return curalloc/1024;
}
/*
extern "C" void debug(int n)
{
char buf[100];
sprintf(buf, "BESTFIT: %d\n", n);
::OutputDebugString(buf);
};
*/// CryMemoryManager.cpp : Defines the entry point for the DLL application.
//#endif //LINUX