root/trunk/dep/src/g3dlite/System.cpp @ 2

Revision 2, 17.1 kB (checked in by yumileroy, 17 years ago)

[svn] * Proper SVN structure

Original author: Neo2003
Date: 2008-10-02 16:23:55-05:00

Line 
1/**
2  @file System.cpp
3 
4  @maintainer Morgan McGuire, matrix@graphics3d.com
5
6  Note: every routine must call init() first.
7
8  There are two kinds of detection used in this file.  At compile
9  time, the _MSC_VER #define is used to determine whether x86 assembly
10  can be used at all.  At runtime, processor detection is used to
11  determine if we can safely call the routines that use that assembly.
12
13  @cite Rob Wyatt http://www.gamasutra.com/features/wyatts_world/19990709/processor_detection_01.htm
14  @cite Benjamin Jurke http://www.flipcode.com/cgi-bin/msg.cgi?showThread=COTD-ProcessorDetectionClass&forum=cotd&id=-1
15  @cite Michael Herf http://www.stereopsis.com/memcpy.html
16
17  @created 2003-01-25
18  @edited  2006-05-17
19 */
20
21#include "G3D/platform.h"
22#include "G3D/System.h"
23#include "G3D/debug.h"
24#include "G3D/format.h"
25
26#ifdef G3D_WIN32
27
28    #include <conio.h>
29    #include <sys/timeb.h>
30    #include "G3D/RegistryUtil.h"
31
32#elif defined(G3D_LINUX)
33
34    #include <stdlib.h>
35    #include <stdio.h>
36    #include <string.h>
37    #include <errno.h>
38    #include <sys/types.h>
39    #include <sys/select.h>
40    #include <termios.h>
41    #include <unistd.h>
42    #include <sys/ioctl.h>
43    #include <sys/time.h>
44    #include <pthread.h>
45
46    // #include <assert.h>
47
48#elif defined(G3D_OSX)
49
50    #include <stdlib.h>
51    #include <stdio.h>
52    #include <errno.h>
53    #include <sys/types.h>
54    #include <sys/sysctl.h>
55    #include <sys/select.h>
56    #include <sys/time.h>
57    #include <termios.h>
58    #include <unistd.h>
59    #include <pthread.h>
60    #include <mach-o/arch.h>
61
62    #include <sstream>
63    #include <CoreServices/CoreServices.h>
64#endif
65
66#if defined(SSE)
67    #include <xmmintrin.h>
68#endif
69
70namespace G3D {
71
72static char                                     versionCstr[1024];
73System::OutOfMemoryCallback                     System::outOfMemoryCallback = NULL;
74
75
76void System::init() {
77    // Cannot use most G3D data structures or utility functions in here because
78    // they are not initialized.
79
80    static bool initialized = false;
81
82    if (initialized) {
83        return;
84    }
85
86    initialized = true;
87
88    if ((G3D_VER % 100) != 0) {
89        sprintf(versionCstr, "G3D %d.%02d beta %d",
90            G3D_VER / 10000,
91            (G3D_VER / 100) % 100,
92            G3D_VER % 100);
93    } else {
94        sprintf(versionCstr, "G3D %d.%02d",
95            G3D_VER / 10000,
96            (G3D_VER / 100) % 100);
97    }
98
99}
100
101
102
103void System::memcpy(void* dst, const void* src, size_t numBytes) {
104        ::memcpy(dst, src, numBytes);
105}
106
107
108void System::memset(void* dst, uint8 value, size_t numBytes) {
109        ::memset(dst, value, numBytes);
110}
111
112
113
114
115
116////////////////////////////////////////////////////////////////
117class BufferPool {
118public:
119
120    /** Only store buffers up to these sizes (in bytes) in each pool->
121        Different pools have different management strategies.
122
123        A large block is preallocated for tiny buffers; they are used with
124        tremendous frequency.  Other buffers are allocated as demanded.
125      */
126    enum {tinyBufferSize = 128, smallBufferSize = 1024, medBufferSize = 4096};
127
128    /**
129       Most buffers we're allowed to store.
130       64000 * 128  = 8 MB (preallocated)
131        1024 * 1024 = 1 MB (allocated on demand)
132        1024 * 4096 = 4 MB (allocated on demand)
133     */
134    enum {maxTinyBuffers = 64000, maxSmallBuffers = 1024, maxMedBuffers = 1024};
135
136private:
137
138    class MemBlock {
139    public:
140        void*           ptr;
141        size_t          bytes;
142
143        inline MemBlock() : ptr(NULL), bytes(0) {}
144        inline MemBlock(void* p, size_t b) : ptr(p), bytes(b) {}
145    };
146
147    MemBlock smallPool[maxSmallBuffers];
148    int smallPoolSize;
149
150    MemBlock medPool[maxMedBuffers];
151    int medPoolSize;
152
153    /** The tiny pool is a single block of storage into which all tiny
154        objects are allocated.  This provides better locality for
155        small objects and avoids the search time, since all tiny
156        blocks are exactly the same size. */
157    void* tinyPool[maxTinyBuffers];
158    int tinyPoolSize;
159
160    /** Pointer to the data in the tiny pool */
161    void* tinyHeap;
162
163#   ifdef G3D_WIN32
164    CRITICAL_SECTION    mutex;
165#   else
166    pthread_mutex_t     mutex;
167#   endif
168
169    /** Provide synchronization between threads */
170    void lock() {
171#       ifdef G3D_WIN32
172            EnterCriticalSection(&mutex);
173#       else
174            pthread_mutex_lock(&mutex);
175#       endif
176    }
177
178    void unlock() {
179#       ifdef G3D_WIN32
180            LeaveCriticalSection(&mutex);
181#       else
182            pthread_mutex_unlock(&mutex);
183#       endif
184    }
185
186    /**
187     Malloc out of the tiny heap.
188     */
189    inline void* tinyMalloc(size_t bytes) {
190        // Note that we ignore the actual byte size
191        // and create a constant size block.
192        (void)bytes;
193        debugAssert(tinyBufferSize >= bytes);
194
195        void* ptr = NULL;
196
197        if (tinyPoolSize > 0) {
198            --tinyPoolSize;
199            // Return the last one
200            ptr = tinyPool[tinyPoolSize];
201        }
202
203        return ptr;
204    }
205
206    /** Returns true if this is a pointer into the tiny heap. */
207    bool inTinyHeap(void* ptr) {
208        return (ptr >= tinyHeap) && 
209               (ptr < (uint8*)tinyHeap + maxTinyBuffers * tinyBufferSize);
210    }
211
212    void tinyFree(void* ptr) {
213        debugAssert(tinyPoolSize < maxTinyBuffers);
214
215        // Put the pointer back into the free list
216        tinyPool[tinyPoolSize] = ptr;
217        ++tinyPoolSize;
218
219    }
220
221    void flushPool(MemBlock* pool, int& poolSize) {
222        for (int i = 0; i < poolSize; ++i) {
223            ::free(pool->ptr);
224            pool->ptr = NULL;
225            pool->bytes = 0;
226        }
227        poolSize = 0;
228    }
229
230
231    /**  Allocate out of a specific pool->  Return NULL if no suitable
232         memory was found.
233   
234         */
235    void* malloc(MemBlock* pool, int& poolSize, size_t bytes) {
236
237        // OPT: find the smallest block that satisfies the request.
238
239        // See if there's something we can use in the buffer pool->
240        // Search backwards since usually we'll re-use the last one.
241        for (int i = (int)poolSize - 1; i >= 0; --i) {
242            if (pool[i].bytes >= bytes) {
243                // We found a suitable entry in the pool->
244
245                // No need to offset the pointer; it is already offset
246                void* ptr = pool[i].ptr;
247
248                // Remove this element from the pool
249                --poolSize;
250                pool[i] = pool[poolSize];
251
252                return ptr;
253            }
254        }
255
256        return NULL;
257    }
258
259public:
260
261    /** Count of memory allocations that have occurred. */
262    int totalMallocs;
263    int mallocsFromTinyPool;
264    int mallocsFromSmallPool;
265    int mallocsFromMedPool;
266
267    /** Amount of memory currently allocated (according to the application).
268        This does not count the memory still remaining in the buffer pool,
269        but does count extra memory required for rounding off to the size
270        of a buffer.
271        Primarily useful for detecting leaks.*/
272    // TODO: make me an atomic int!
273    int bytesAllocated;
274
275    BufferPool() {
276        totalMallocs         = 0;
277
278        mallocsFromTinyPool  = 0;
279        mallocsFromSmallPool = 0;
280        mallocsFromMedPool   = 0;
281
282        bytesAllocated       = true;
283
284        tinyPoolSize         = 0;
285        tinyHeap             = NULL;
286
287        smallPoolSize        = 0;
288
289        medPoolSize          = 0;
290
291
292        // Initialize the tiny heap as a bunch of pointers into one
293        // pre-allocated buffer.
294        tinyHeap = ::malloc(maxTinyBuffers * tinyBufferSize);
295        for (int i = 0; i < maxTinyBuffers; ++i) {
296            tinyPool[i] = (uint8*)tinyHeap + (tinyBufferSize * i);
297        }
298        tinyPoolSize = maxTinyBuffers;
299
300#       ifdef G3D_WIN32
301            InitializeCriticalSection(&mutex);
302#       else
303            pthread_mutex_init(&mutex, NULL);
304#       endif
305    }
306
307
308    ~BufferPool() {
309        ::free(tinyHeap);
310#       ifdef G3D_WIN32
311            DeleteCriticalSection(&mutex);
312#       else
313            // No destruction on pthreads
314#       endif
315    }
316
317   
318    void* realloc(void* ptr, size_t bytes) {
319        if (ptr == NULL) {
320            return malloc(bytes);
321        }
322
323        if (inTinyHeap(ptr)) {
324            if (bytes <= tinyBufferSize) {
325                // The old pointer actually had enough space.
326                return ptr;
327            } else {
328                // Free the old pointer and malloc
329               
330                void* newPtr = malloc(bytes);
331                System::memcpy(newPtr, ptr, tinyBufferSize);
332                tinyFree(ptr);
333                return newPtr;
334
335            }
336        } else {
337            // In one of our heaps.
338
339            // See how big the block really was
340            size_t realSize = ((uint32*)ptr)[-1];
341            if (bytes <= realSize) {
342                // The old block was big enough.
343                return ptr;
344            }
345
346            // Need to reallocate
347            void* newPtr = malloc(bytes);
348            System::memcpy(newPtr, ptr, realSize);
349            free(ptr);
350            return newPtr;
351        }
352    }
353
354
355    void* malloc(size_t bytes) {
356        lock();
357        ++totalMallocs;
358
359        if (bytes <= tinyBufferSize) {
360
361            void* ptr = tinyMalloc(bytes);
362
363            if (ptr) {
364                ++mallocsFromTinyPool;
365                unlock();
366                return ptr;
367            }
368
369        } 
370       
371        // Failure to allocate a tiny buffer is allowed to flow
372        // through to a small buffer
373        if (bytes <= smallBufferSize) {
374           
375            void* ptr = malloc(smallPool, smallPoolSize, bytes);
376
377            if (ptr) {
378                ++mallocsFromSmallPool;
379                unlock();
380                return ptr;
381            }
382
383        } else  if (bytes <= medBufferSize) {
384            // Note that a small allocation failure does *not* fall
385            // through into a medium allocation because that would
386            // waste the medium buffer's resources.
387
388            void* ptr = malloc(medPool, medPoolSize, bytes);
389
390            if (ptr) {
391                ++mallocsFromMedPool;
392                unlock();
393                return ptr;
394            }
395        }
396
397        bytesAllocated += 4 + (int) bytes;
398        unlock();
399
400        // Heap allocate
401
402        // Allocate 4 extra bytes for our size header (unfortunate,
403        // since malloc already added its own header).
404        void* ptr = ::malloc(bytes + 4);
405
406        if (ptr == NULL) {
407            // Flush memory pools to try and recover space
408            flushPool(smallPool, smallPoolSize);
409            flushPool(medPool, medPoolSize);
410            ptr = ::malloc(bytes + 4);
411        }
412
413
414        if (ptr == NULL) {
415            if ((System::outOfMemoryCallback != NULL) &&
416                (System::outOfMemoryCallback(bytes + 4, true) == true)) {
417                // Re-attempt the malloc
418                ptr = ::malloc(bytes + 4);
419            }
420        }
421
422        if (ptr == NULL) {
423            if (System::outOfMemoryCallback != NULL) {
424                // Notify the application
425                System::outOfMemoryCallback(bytes + 4, false);
426            }
427            return NULL;
428        }
429
430        *(uint32*)ptr = (uint32)bytes;
431
432        return (uint8*)ptr + 4;
433    }
434
435
436    void free(void* ptr) {
437        if (ptr == NULL) {
438            // Free does nothing on null pointers
439            return;
440        }
441
442        debugAssert(isValidPointer(ptr));
443
444        if (inTinyHeap(ptr)) {
445            lock();
446            tinyFree(ptr);
447            unlock();
448            return;
449        }
450
451        uint32 bytes = ((uint32*)ptr)[-1];
452
453        lock();
454        if (bytes <= smallBufferSize) {
455            if (smallPoolSize < maxSmallBuffers) {
456                smallPool[smallPoolSize] = MemBlock(ptr, bytes);
457                ++smallPoolSize;
458                unlock();
459                return;
460            }
461        } else if (bytes <= medBufferSize) {
462            if (medPoolSize < maxMedBuffers) {
463                medPool[medPoolSize] = MemBlock(ptr, bytes);
464                ++medPoolSize;
465                unlock();
466                return;
467            }
468        }
469        bytesAllocated -= bytes + 4;
470        unlock();
471
472        // Free; the buffer pools are full or this is too big to store.
473        ::free((uint8*)ptr - 4);
474    }
475
476    std::string performance() const {
477        if (totalMallocs > 0) {
478            int pooled = mallocsFromTinyPool +
479                         mallocsFromSmallPool + 
480                         mallocsFromMedPool;
481
482            int total = totalMallocs;
483
484            return format("malloc performance: %5.1f%% <= %db, %5.1f%% <= %db, "
485                          "%5.1f%% <= %db, %5.1f%% > %db",
486                          100.0 * mallocsFromTinyPool  / total,
487                          BufferPool::tinyBufferSize,
488                          100.0 * mallocsFromSmallPool / total,
489                          BufferPool::smallBufferSize,
490                          100.0 * mallocsFromMedPool   / total,
491                          BufferPool::medBufferSize,
492                          100.0 * (1.0 - (double)pooled / total),
493                          BufferPool::medBufferSize);
494        } else {
495            return "No System::malloc calls made yet.";
496        }
497    }
498
499    std::string status() const {
500        return format("preallocated shared buffers: %5d/%d x %db",
501            maxTinyBuffers - tinyPoolSize, maxTinyBuffers, tinyBufferSize);
502    }
503};
504
505// Dynamically allocated because we need to ensure that
506// the buffer pool is still around when the last global variable
507// is deallocated.
508static BufferPool* bufferpool = NULL;
509
510std::string System::mallocPerformance() {   
511#ifndef NO_BUFFERPOOL
512    return bufferpool->performance();
513#else
514        return "NO_BUFFERPOOL";
515#endif
516}
517
518std::string System::mallocStatus() {   
519#ifndef NO_BUFFERPOOL
520    return bufferpool->status();
521#else
522        return "NO_BUFFERPOOL";
523#endif
524}
525
526
527void System::resetMallocPerformanceCounters() {
528#ifndef NO_BUFFERPOOL
529    bufferpool->totalMallocs         = 0;
530    bufferpool->mallocsFromMedPool   = 0;
531    bufferpool->mallocsFromSmallPool = 0;
532    bufferpool->mallocsFromTinyPool  = 0;
533#endif
534}
535
536
537#ifndef NO_BUFFERPOOL
538inline void initMem() {
539    // Putting the test here ensures that the system is always
540    // initialized, even when globals are being allocated.
541    static bool initialized = false;
542    if (! initialized) {
543        bufferpool = new BufferPool();
544        initialized = true;
545    }
546}
547#endif
548
549
550void* System::malloc(size_t bytes) {
551#ifndef NO_BUFFERPOOL
552    initMem();
553    return bufferpool->malloc(bytes);
554#else
555    return ::malloc(bytes);
556#endif
557}
558
559void* System::calloc(size_t n, size_t x) {
560#ifndef NO_BUFFERPOOL
561    void* b = System::malloc(n * x);
562    System::memset(b, 0, n * x);
563    return b;
564#else
565    return ::calloc(n, x);
566#endif
567}
568
569
570void* System::realloc(void* block, size_t bytes) {
571#ifndef NO_BUFFERPOOL
572    initMem();
573    return bufferpool->realloc(block, bytes);
574#else
575        return ::realloc(block, bytes);
576#endif
577}
578
579
580void System::free(void* p) {
581#ifndef NO_BUFFERPOOL
582    bufferpool->free(p);
583#else
584        return ::free(p);
585#endif
586}
587
588
589void* System::alignedMalloc(size_t bytes, size_t alignment) {
590    alwaysAssertM(isPow2(alignment), "alignment must be a power of 2");
591
592    // We must align to at least a word boundary.
593    alignment = iMax((int)alignment, sizeof(void *));
594
595    // Pad the allocation size with the alignment size and the
596    // size of the redirect pointer.
597    size_t totalBytes = bytes + alignment + sizeof(intptr_t);
598
599    void* truePtr = System::malloc(totalBytes);
600
601    if (!truePtr) {
602        // malloc returned NULL
603        return NULL;
604    }
605
606    debugAssert(isValidHeapPointer(truePtr));
607    #ifdef G3D_WIN32
608    // The blocks we return will not be valid Win32 debug heap
609    // pointers because they are offset
610    //  debugAssert(_CrtIsValidPointer(truePtr, totalBytes, TRUE) );
611    #endif
612
613    // The return pointer will be the next aligned location (we must at least
614    // leave space for the redirect pointer, however).
615    char* alignedPtr = ((char*)truePtr)+ sizeof(intptr_t);
616
617#if 0
618    // 2^n - 1 has the form 1111... in binary.
619    uint32 bitMask = (alignment - 1);
620
621    // Advance forward until we reach an aligned location.
622    while ((((intptr_t)alignedPtr) & bitMask) != 0) {
623        alignedPtr += sizeof(void*);
624    }
625#else
626    alignedPtr += alignment - (((intptr_t)alignedPtr) & (alignment - 1));
627    // assert((alignedPtr - truePtr) + bytes <= totalBytes);
628#endif
629
630    debugAssert((alignedPtr - truePtr) + bytes <= totalBytes);
631
632    // Immediately before the aligned location, write the true array location
633    // so that we can free it correctly.
634    intptr_t* redirectPtr = (intptr_t*)(alignedPtr - sizeof(intptr_t));
635    redirectPtr[0] = (intptr_t)truePtr;
636
637    debugAssert(isValidHeapPointer(truePtr));
638
639    #ifdef G3D_WIN32
640        debugAssert( _CrtIsValidPointer(alignedPtr, bytes, TRUE) );
641    #endif
642    return (void*)alignedPtr;
643}
644
645
646void System::alignedFree(void* _ptr) {
647    if (_ptr == NULL) {
648        return;
649    }
650
651    char* alignedPtr = (char*)_ptr;
652
653    // Back up one word from the pointer the user passed in.
654    // We now have a pointer to a pointer to the true start
655    // of the memory block.
656    intptr_t* redirectPtr = (intptr_t*)(alignedPtr - sizeof(intptr_t));
657
658    // Dereference that pointer so that ptr = true start
659    void* truePtr = (void*)(redirectPtr[0]);
660
661    debugAssert(isValidHeapPointer(truePtr));
662    System::free(truePtr);
663}
664
665
666}  // namespace
Note: See TracBrowser for help on using the browser.