From 9b1c0e006f20091f28f3f468cfcab1feb51286bd Mon Sep 17 00:00:00 2001 From: Neo2003 Date: Thu, 2 Oct 2008 16:23:55 -0500 Subject: [svn] * Proper SVN structure --HG-- branch : trunk --- dep/src/g3dlite/System.cpp | 666 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 666 insertions(+) create mode 100644 dep/src/g3dlite/System.cpp (limited to 'dep/src/g3dlite/System.cpp') diff --git a/dep/src/g3dlite/System.cpp b/dep/src/g3dlite/System.cpp new file mode 100644 index 00000000000..06be7cd25e0 --- /dev/null +++ b/dep/src/g3dlite/System.cpp @@ -0,0 +1,666 @@ +/** + @file System.cpp + + @maintainer Morgan McGuire, matrix@graphics3d.com + + Note: every routine must call init() first. + + There are two kinds of detection used in this file. At compile + time, the _MSC_VER #define is used to determine whether x86 assembly + can be used at all. At runtime, processor detection is used to + determine if we can safely call the routines that use that assembly. + + @cite Rob Wyatt http://www.gamasutra.com/features/wyatts_world/19990709/processor_detection_01.htm + @cite Benjamin Jurke http://www.flipcode.com/cgi-bin/msg.cgi?showThread=COTD-ProcessorDetectionClass&forum=cotd&id=-1 + @cite Michael Herf http://www.stereopsis.com/memcpy.html + + @created 2003-01-25 + @edited 2006-05-17 + */ + +#include "G3D/platform.h" +#include "G3D/System.h" +#include "G3D/debug.h" +#include "G3D/format.h" + +#ifdef G3D_WIN32 + + #include + #include + #include "G3D/RegistryUtil.h" + +#elif defined(G3D_LINUX) + + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + + // #include + +#elif defined(G3D_OSX) + + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + + #include + #include +#endif + +#if defined(SSE) + #include +#endif + +namespace G3D { + +static char versionCstr[1024]; +System::OutOfMemoryCallback System::outOfMemoryCallback = NULL; + + +void System::init() { + // Cannot use most G3D data structures or utility functions in here because + // they are not initialized. + + static bool initialized = false; + + if (initialized) { + return; + } + + initialized = true; + + if ((G3D_VER % 100) != 0) { + sprintf(versionCstr, "G3D %d.%02d beta %d", + G3D_VER / 10000, + (G3D_VER / 100) % 100, + G3D_VER % 100); + } else { + sprintf(versionCstr, "G3D %d.%02d", + G3D_VER / 10000, + (G3D_VER / 100) % 100); + } + +} + + + +void System::memcpy(void* dst, const void* src, size_t numBytes) { + ::memcpy(dst, src, numBytes); +} + + +void System::memset(void* dst, uint8 value, size_t numBytes) { + ::memset(dst, value, numBytes); +} + + + + + +//////////////////////////////////////////////////////////////// +class BufferPool { +public: + + /** Only store buffers up to these sizes (in bytes) in each pool-> + Different pools have different management strategies. + + A large block is preallocated for tiny buffers; they are used with + tremendous frequency. Other buffers are allocated as demanded. + */ + enum {tinyBufferSize = 128, smallBufferSize = 1024, medBufferSize = 4096}; + + /** + Most buffers we're allowed to store. + 64000 * 128 = 8 MB (preallocated) + 1024 * 1024 = 1 MB (allocated on demand) + 1024 * 4096 = 4 MB (allocated on demand) + */ + enum {maxTinyBuffers = 64000, maxSmallBuffers = 1024, maxMedBuffers = 1024}; + +private: + + class MemBlock { + public: + void* ptr; + size_t bytes; + + inline MemBlock() : ptr(NULL), bytes(0) {} + inline MemBlock(void* p, size_t b) : ptr(p), bytes(b) {} + }; + + MemBlock smallPool[maxSmallBuffers]; + int smallPoolSize; + + MemBlock medPool[maxMedBuffers]; + int medPoolSize; + + /** The tiny pool is a single block of storage into which all tiny + objects are allocated. This provides better locality for + small objects and avoids the search time, since all tiny + blocks are exactly the same size. */ + void* tinyPool[maxTinyBuffers]; + int tinyPoolSize; + + /** Pointer to the data in the tiny pool */ + void* tinyHeap; + +# ifdef G3D_WIN32 + CRITICAL_SECTION mutex; +# else + pthread_mutex_t mutex; +# endif + + /** Provide synchronization between threads */ + void lock() { +# ifdef G3D_WIN32 + EnterCriticalSection(&mutex); +# else + pthread_mutex_lock(&mutex); +# endif + } + + void unlock() { +# ifdef G3D_WIN32 + LeaveCriticalSection(&mutex); +# else + pthread_mutex_unlock(&mutex); +# endif + } + + /** + Malloc out of the tiny heap. + */ + inline void* tinyMalloc(size_t bytes) { + // Note that we ignore the actual byte size + // and create a constant size block. + (void)bytes; + debugAssert(tinyBufferSize >= bytes); + + void* ptr = NULL; + + if (tinyPoolSize > 0) { + --tinyPoolSize; + // Return the last one + ptr = tinyPool[tinyPoolSize]; + } + + return ptr; + } + + /** Returns true if this is a pointer into the tiny heap. */ + bool inTinyHeap(void* ptr) { + return (ptr >= tinyHeap) && + (ptr < (uint8*)tinyHeap + maxTinyBuffers * tinyBufferSize); + } + + void tinyFree(void* ptr) { + debugAssert(tinyPoolSize < maxTinyBuffers); + + // Put the pointer back into the free list + tinyPool[tinyPoolSize] = ptr; + ++tinyPoolSize; + + } + + void flushPool(MemBlock* pool, int& poolSize) { + for (int i = 0; i < poolSize; ++i) { + ::free(pool->ptr); + pool->ptr = NULL; + pool->bytes = 0; + } + poolSize = 0; + } + + + /** Allocate out of a specific pool-> Return NULL if no suitable + memory was found. + + */ + void* malloc(MemBlock* pool, int& poolSize, size_t bytes) { + + // OPT: find the smallest block that satisfies the request. + + // See if there's something we can use in the buffer pool-> + // Search backwards since usually we'll re-use the last one. + for (int i = (int)poolSize - 1; i >= 0; --i) { + if (pool[i].bytes >= bytes) { + // We found a suitable entry in the pool-> + + // No need to offset the pointer; it is already offset + void* ptr = pool[i].ptr; + + // Remove this element from the pool + --poolSize; + pool[i] = pool[poolSize]; + + return ptr; + } + } + + return NULL; + } + +public: + + /** Count of memory allocations that have occurred. */ + int totalMallocs; + int mallocsFromTinyPool; + int mallocsFromSmallPool; + int mallocsFromMedPool; + + /** Amount of memory currently allocated (according to the application). + This does not count the memory still remaining in the buffer pool, + but does count extra memory required for rounding off to the size + of a buffer. + Primarily useful for detecting leaks.*/ + // TODO: make me an atomic int! + int bytesAllocated; + + BufferPool() { + totalMallocs = 0; + + mallocsFromTinyPool = 0; + mallocsFromSmallPool = 0; + mallocsFromMedPool = 0; + + bytesAllocated = true; + + tinyPoolSize = 0; + tinyHeap = NULL; + + smallPoolSize = 0; + + medPoolSize = 0; + + + // Initialize the tiny heap as a bunch of pointers into one + // pre-allocated buffer. + tinyHeap = ::malloc(maxTinyBuffers * tinyBufferSize); + for (int i = 0; i < maxTinyBuffers; ++i) { + tinyPool[i] = (uint8*)tinyHeap + (tinyBufferSize * i); + } + tinyPoolSize = maxTinyBuffers; + +# ifdef G3D_WIN32 + InitializeCriticalSection(&mutex); +# else + pthread_mutex_init(&mutex, NULL); +# endif + } + + + ~BufferPool() { + ::free(tinyHeap); +# ifdef G3D_WIN32 + DeleteCriticalSection(&mutex); +# else + // No destruction on pthreads +# endif + } + + + void* realloc(void* ptr, size_t bytes) { + if (ptr == NULL) { + return malloc(bytes); + } + + if (inTinyHeap(ptr)) { + if (bytes <= tinyBufferSize) { + // The old pointer actually had enough space. + return ptr; + } else { + // Free the old pointer and malloc + + void* newPtr = malloc(bytes); + System::memcpy(newPtr, ptr, tinyBufferSize); + tinyFree(ptr); + return newPtr; + + } + } else { + // In one of our heaps. + + // See how big the block really was + size_t realSize = ((uint32*)ptr)[-1]; + if (bytes <= realSize) { + // The old block was big enough. + return ptr; + } + + // Need to reallocate + void* newPtr = malloc(bytes); + System::memcpy(newPtr, ptr, realSize); + free(ptr); + return newPtr; + } + } + + + void* malloc(size_t bytes) { + lock(); + ++totalMallocs; + + if (bytes <= tinyBufferSize) { + + void* ptr = tinyMalloc(bytes); + + if (ptr) { + ++mallocsFromTinyPool; + unlock(); + return ptr; + } + + } + + // Failure to allocate a tiny buffer is allowed to flow + // through to a small buffer + if (bytes <= smallBufferSize) { + + void* ptr = malloc(smallPool, smallPoolSize, bytes); + + if (ptr) { + ++mallocsFromSmallPool; + unlock(); + return ptr; + } + + } else if (bytes <= medBufferSize) { + // Note that a small allocation failure does *not* fall + // through into a medium allocation because that would + // waste the medium buffer's resources. + + void* ptr = malloc(medPool, medPoolSize, bytes); + + if (ptr) { + ++mallocsFromMedPool; + unlock(); + return ptr; + } + } + + bytesAllocated += 4 + (int) bytes; + unlock(); + + // Heap allocate + + // Allocate 4 extra bytes for our size header (unfortunate, + // since malloc already added its own header). + void* ptr = ::malloc(bytes + 4); + + if (ptr == NULL) { + // Flush memory pools to try and recover space + flushPool(smallPool, smallPoolSize); + flushPool(medPool, medPoolSize); + ptr = ::malloc(bytes + 4); + } + + + if (ptr == NULL) { + if ((System::outOfMemoryCallback != NULL) && + (System::outOfMemoryCallback(bytes + 4, true) == true)) { + // Re-attempt the malloc + ptr = ::malloc(bytes + 4); + } + } + + if (ptr == NULL) { + if (System::outOfMemoryCallback != NULL) { + // Notify the application + System::outOfMemoryCallback(bytes + 4, false); + } + return NULL; + } + + *(uint32*)ptr = (uint32)bytes; + + return (uint8*)ptr + 4; + } + + + void free(void* ptr) { + if (ptr == NULL) { + // Free does nothing on null pointers + return; + } + + debugAssert(isValidPointer(ptr)); + + if (inTinyHeap(ptr)) { + lock(); + tinyFree(ptr); + unlock(); + return; + } + + uint32 bytes = ((uint32*)ptr)[-1]; + + lock(); + if (bytes <= smallBufferSize) { + if (smallPoolSize < maxSmallBuffers) { + smallPool[smallPoolSize] = MemBlock(ptr, bytes); + ++smallPoolSize; + unlock(); + return; + } + } else if (bytes <= medBufferSize) { + if (medPoolSize < maxMedBuffers) { + medPool[medPoolSize] = MemBlock(ptr, bytes); + ++medPoolSize; + unlock(); + return; + } + } + bytesAllocated -= bytes + 4; + unlock(); + + // Free; the buffer pools are full or this is too big to store. + ::free((uint8*)ptr - 4); + } + + std::string performance() const { + if (totalMallocs > 0) { + int pooled = mallocsFromTinyPool + + mallocsFromSmallPool + + mallocsFromMedPool; + + int total = totalMallocs; + + return format("malloc performance: %5.1f%% <= %db, %5.1f%% <= %db, " + "%5.1f%% <= %db, %5.1f%% > %db", + 100.0 * mallocsFromTinyPool / total, + BufferPool::tinyBufferSize, + 100.0 * mallocsFromSmallPool / total, + BufferPool::smallBufferSize, + 100.0 * mallocsFromMedPool / total, + BufferPool::medBufferSize, + 100.0 * (1.0 - (double)pooled / total), + BufferPool::medBufferSize); + } else { + return "No System::malloc calls made yet."; + } + } + + std::string status() const { + return format("preallocated shared buffers: %5d/%d x %db", + maxTinyBuffers - tinyPoolSize, maxTinyBuffers, tinyBufferSize); + } +}; + +// Dynamically allocated because we need to ensure that +// the buffer pool is still around when the last global variable +// is deallocated. +static BufferPool* bufferpool = NULL; + +std::string System::mallocPerformance() { +#ifndef NO_BUFFERPOOL + return bufferpool->performance(); +#else + return "NO_BUFFERPOOL"; +#endif +} + +std::string System::mallocStatus() { +#ifndef NO_BUFFERPOOL + return bufferpool->status(); +#else + return "NO_BUFFERPOOL"; +#endif +} + + +void System::resetMallocPerformanceCounters() { +#ifndef NO_BUFFERPOOL + bufferpool->totalMallocs = 0; + bufferpool->mallocsFromMedPool = 0; + bufferpool->mallocsFromSmallPool = 0; + bufferpool->mallocsFromTinyPool = 0; +#endif +} + + +#ifndef NO_BUFFERPOOL +inline void initMem() { + // Putting the test here ensures that the system is always + // initialized, even when globals are being allocated. + static bool initialized = false; + if (! initialized) { + bufferpool = new BufferPool(); + initialized = true; + } +} +#endif + + +void* System::malloc(size_t bytes) { +#ifndef NO_BUFFERPOOL + initMem(); + return bufferpool->malloc(bytes); +#else + return ::malloc(bytes); +#endif +} + +void* System::calloc(size_t n, size_t x) { +#ifndef NO_BUFFERPOOL + void* b = System::malloc(n * x); + System::memset(b, 0, n * x); + return b; +#else + return ::calloc(n, x); +#endif +} + + +void* System::realloc(void* block, size_t bytes) { +#ifndef NO_BUFFERPOOL + initMem(); + return bufferpool->realloc(block, bytes); +#else + return ::realloc(block, bytes); +#endif +} + + +void System::free(void* p) { +#ifndef NO_BUFFERPOOL + bufferpool->free(p); +#else + return ::free(p); +#endif +} + + +void* System::alignedMalloc(size_t bytes, size_t alignment) { + alwaysAssertM(isPow2(alignment), "alignment must be a power of 2"); + + // We must align to at least a word boundary. + alignment = iMax((int)alignment, sizeof(void *)); + + // Pad the allocation size with the alignment size and the + // size of the redirect pointer. + size_t totalBytes = bytes + alignment + sizeof(intptr_t); + + void* truePtr = System::malloc(totalBytes); + + if (!truePtr) { + // malloc returned NULL + return NULL; + } + + debugAssert(isValidHeapPointer(truePtr)); + #ifdef G3D_WIN32 + // The blocks we return will not be valid Win32 debug heap + // pointers because they are offset + // debugAssert(_CrtIsValidPointer(truePtr, totalBytes, TRUE) ); + #endif + + // The return pointer will be the next aligned location (we must at least + // leave space for the redirect pointer, however). + char* alignedPtr = ((char*)truePtr)+ sizeof(intptr_t); + +#if 0 + // 2^n - 1 has the form 1111... in binary. + uint32 bitMask = (alignment - 1); + + // Advance forward until we reach an aligned location. + while ((((intptr_t)alignedPtr) & bitMask) != 0) { + alignedPtr += sizeof(void*); + } +#else + alignedPtr += alignment - (((intptr_t)alignedPtr) & (alignment - 1)); + // assert((alignedPtr - truePtr) + bytes <= totalBytes); +#endif + + debugAssert((alignedPtr - truePtr) + bytes <= totalBytes); + + // Immediately before the aligned location, write the true array location + // so that we can free it correctly. + intptr_t* redirectPtr = (intptr_t*)(alignedPtr - sizeof(intptr_t)); + redirectPtr[0] = (intptr_t)truePtr; + + debugAssert(isValidHeapPointer(truePtr)); + + #ifdef G3D_WIN32 + debugAssert( _CrtIsValidPointer(alignedPtr, bytes, TRUE) ); + #endif + return (void*)alignedPtr; +} + + +void System::alignedFree(void* _ptr) { + if (_ptr == NULL) { + return; + } + + char* alignedPtr = (char*)_ptr; + + // Back up one word from the pointer the user passed in. + // We now have a pointer to a pointer to the true start + // of the memory block. + intptr_t* redirectPtr = (intptr_t*)(alignedPtr - sizeof(intptr_t)); + + // Dereference that pointer so that ptr = true start + void* truePtr = (void*)(redirectPtr[0]); + + debugAssert(isValidHeapPointer(truePtr)); + System::free(truePtr); +} + + +} // namespace -- cgit v1.2.3