IPP Software Navigation Tools IPP Links Communication Pan-STARRS Links

Ignore:
Timestamp:
Jun 22, 2011, 12:27:33 AM (15 years ago)
Author:
eugene
Message:

merged from eam_branches/ipp-20110505: update memory locking to allow paralled realloc

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/psLib/src/sys/psMemory.c

    r31152 r31660  
    3535#include "psError.h"    // for psErrorStackPrint() only
    3636#include "psMemory.h"
     37#include "psAbort.h"
    3738
    3839// Magic number in psMemBlock header
    3940#define P_PS_MEMMAGIC (uint32_t)0xdeadbeef
    4041
     42// The psLib memory tools require locking to manipulate the linked list which holds the memory
     43// structure.  There are two major options which can be invoked, given below. 
     44
     45// USE_SPINLOCK replaces the mutex in this code block with a spinlock.  This might be faster in
     46// some cases, but probably not enough to matter.  The disadavantages are: (1) programs would
     47// need to call the function 'psMemInit' (which they currently do not in general); (2)
     48// single-processor machines are poorly set up to use spinlocks; (3) not all compilers /
     49// machine combinations support spinlocks.  This option is NOT recommended
     50
     51// USE_HARDLOCK replaces the per-memBlock locking that is the minimum required to protect the
     52// psRealloc function with pure mutex locking that holds the lock during the (possibly
     53// expensive) realloc call.  At this point, it does not seem clear that there is a substantial
     54// gain in processing speed from using the per-memBlock locking, but more testing may reveal
     55// cases where it matters. 
     56
     57# define USE_SPINLOCK 0
     58# define USE_HARDLOCK 0
     59
     60# if (USE_SPINLOCK)
     61#define MUTEX_LOCK(mutexPtr) \
     62if (safeThreads) { \
     63    pthread_spin_lock(mutexPtr); \
     64}
     65#define MUTEX_UNLOCK(mutexPtr) \
     66if (safeThreads) { \
     67    pthread_spin_unlock(mutexPtr); \
     68}
     69# else // !USE_SPINLOCK
    4170#define MUTEX_LOCK(mutexPtr) \
    4271if (safeThreads) { \
    4372    pthread_mutex_lock(mutexPtr); \
    4473}
    45 
    4674#define MUTEX_UNLOCK(mutexPtr) \
    4775if (safeThreads) { \
    4876    pthread_mutex_unlock(mutexPtr); \
    4977}
     78# endif // USE_SPINLOCK
    5079
    5180// psAbort() calls functions that call psAlloc() so it is *UNSAFE* to use it
     
    93122//
    94123//
     124
     125# if (USE_SPINLOCK)
     126static pthread_spinlock_t memBlockListMutex;
     127# else
    95128static pthread_mutex_t memBlockListMutex = PTHREAD_MUTEX_INITIALIZER;
     129# endif
     130
     131void psMemInit() {
     132
     133# if (USE_SPINLOCK)
     134    pthread_spin_init(&memBlockListMutex, 0);
     135# endif
     136
     137}
    96138
    97139/******** thread safety options management ********/
     
    325367}
    326368
     369/** BLOCK_LOCK, BLOCK_UNLOCK, BLOCKSET_LOCK, BLOCKSET_UNLOCK are used to set per-memBlock locks
     370 **     to allow psRealloc to release the global lock before performing the expensive system
     371 **     call 'realloc'
     372 **/
     373
     374# if (!USE_HARDLOCK)
     375static int setLock = 0;
     376static int clearLock = 0;
     377static int retryLock = 0;
     378# endif
     379
     380# define BLOCK_SLEEP 100
     381
     382# define MEM_ASSERT(COND,MSG) { if (!(COND)) { fprintf (stderr, MSG); abort(); } }
     383
    327384// pointer to the last mem block that was allocated.
    328385// This is the root of the entire memory list
    329386static psMemBlock *lastMemBlockAllocated = NULL;
     387
     388// set lock on lastMemBlockAllocated
     389void BLOCKLAST_LOCK () {
     390
     391# if (USE_HARDLOCK)
     392    MUTEX_LOCK(&memBlockListMutex);
     393# else
     394
     395    // if memBlock is not defined, we are just asking for a m
     396    if (!lastMemBlockAllocated) {
     397        MUTEX_LOCK(&memBlockListMutex);
     398        return;
     399    }
     400       
     401    while (true) {
     402        // set a lock, then lock this memblock and its neighbors
     403        MUTEX_LOCK(&memBlockListMutex);
     404   
     405        // did we beat the race condition?  is the lock still clear?
     406        if (lastMemBlockAllocated->inFlight) {
     407            // nope, we need to try again
     408            MUTEX_UNLOCK(&memBlockListMutex);
     409            retryLock ++;
     410            usleep (BLOCK_SLEEP);
     411            continue;
     412        }
     413
     414        // the marker is ours!
     415        lastMemBlockAllocated->inFlight = true;
     416        setLock ++;
     417        return;
     418    }
     419# endif
     420}
     421
     422// memBlock->inFlight is a 'soft' lock.  if true, the lock is set
     423void BLOCK_LOCK (psMemBlock *memBlock, bool keepMutex) {
     424
     425# if (USE_HARDLOCK)
     426    MUTEX_LOCK(&memBlockListMutex);
     427# else
     428
     429    MEM_ASSERT (memBlock || keepMutex, "trying to set a soft lock on a non-existent memBlock\n");
     430
     431    // if memBlock is not defined, we are just asking for a m
     432    if (!memBlock) {
     433        MUTEX_LOCK(&memBlockListMutex);
     434        return;
     435    }
     436       
     437    while (true) {
     438        // set a lock, then lock this memblock and its neighbors
     439        MUTEX_LOCK(&memBlockListMutex);
     440   
     441        // did we beat the race condition?  is the lock still clear?
     442        if (memBlock->inFlight) {
     443            // nope, we need to try again
     444            MUTEX_UNLOCK(&memBlockListMutex);
     445            usleep (BLOCK_SLEEP);
     446            retryLock ++;
     447            continue;
     448        }
     449
     450        // the marker is ours!
     451        memBlock->inFlight = true;
     452        setLock ++;
     453        if (keepMutex) return;
     454
     455        MUTEX_UNLOCK(&memBlockListMutex);
     456        return;
     457    }
     458# endif
     459}
     460
     461void BLOCK_UNLOCK (psMemBlock *memBlock, bool haveMutex) {
     462   
     463# if (USE_HARDLOCK)
     464    MUTEX_UNLOCK(&memBlockListMutex);
     465# else
     466
     467    MEM_ASSERT (memBlock || haveMutex, "trying to clear a soft lock on a non-existent memBlock\n");
     468
     469    // if memBlock is not defined, we are just asking for a regular lock
     470    if (!memBlock) {
     471        MUTEX_UNLOCK(&memBlockListMutex);
     472        return;
     473    }
     474
     475    MEM_ASSERT (memBlock->inFlight, "trying to clear an unlocked memBlock\n");
     476
     477    // need to lock before clearing the marker
     478    if (!haveMutex) {
     479        MUTEX_LOCK(&memBlockListMutex);
     480    }
     481    memBlock->inFlight = false;
     482    clearLock ++;
     483    MUTEX_UNLOCK(&memBlockListMutex);
     484    return;
     485# endif   
     486}
     487
     488// attempt to grab the markers on the given memBlock and its two neighbors.  the only times the
     489// two neighbors do not exist is if we are at the beginning or end of the list (or the list is
     490// new).
     491void BLOCKSET_LOCK (psMemBlock *memBlock, bool keepMutex) {
     492
     493# if (USE_HARDLOCK)
     494    MUTEX_LOCK(&memBlockListMutex);
     495# else
     496
     497    MEM_ASSERT (memBlock, "trying to set a soft lock on a non-existent memBlock\n");
     498
     499    while (true) {
     500        // we cannot set the lock on the marker while the lock is held
     501        // wait until all three markers are clear:
     502        // while (memBlock->inFlight) { usleep (BLOCK_SLEEP); }
     503        // while (memBlock->nextBlock && memBlock->nextBlock->inFlight) { usleep (BLOCK_SLEEP); }
     504        // while (memBlock->previousBlock && memBlock->previousBlock->inFlight) { usleep (BLOCK_SLEEP); }
     505
     506        // set a lock, then lock this memblock and its neighbors
     507        MUTEX_LOCK(&memBlockListMutex);
     508   
     509        // did we beat the race condition?  are all three locks still clear?
     510        if (memBlock->inFlight) {
     511            // nope, we need to try again
     512            MUTEX_UNLOCK(&memBlockListMutex);
     513            usleep (BLOCK_SLEEP);
     514            retryLock ++;
     515            continue;
     516        }
     517        if (memBlock->nextBlock && memBlock->nextBlock->inFlight) {
     518            // nope, we need to try again
     519            MUTEX_UNLOCK(&memBlockListMutex);
     520            usleep (BLOCK_SLEEP);
     521            retryLock ++;
     522            continue;
     523        }
     524        if (memBlock->previousBlock && memBlock->previousBlock->inFlight) {
     525            // nope, we need to try again
     526            MUTEX_UNLOCK(&memBlockListMutex);
     527            usleep (BLOCK_SLEEP);
     528            retryLock ++;
     529            continue;
     530        }
     531
     532        // the markers are ours!
     533        memBlock->inFlight = true;
     534        setLock ++;
     535        if (memBlock->nextBlock) {
     536            memBlock->nextBlock->inFlight = true;
     537        }
     538        if (memBlock->previousBlock) {
     539            memBlock->previousBlock->inFlight = true;
     540        }
     541        if (keepMutex) return;
     542
     543        MUTEX_UNLOCK(&memBlockListMutex);
     544        return;
     545    }
     546# endif
     547}
     548
     549void BLOCKSET_UNLOCK (psMemBlock *memBlock, bool haveMutex) {
     550   
     551# if (USE_HARDLOCK)
     552    MUTEX_UNLOCK(&memBlockListMutex);
     553# else
     554
     555    // need to lock before clearing the marker
     556    if (!haveMutex) {
     557        MUTEX_LOCK(&memBlockListMutex);
     558    }
     559    MEM_ASSERT (memBlock->inFlight, "trying to clear an unlocked memBlock\n");
     560    memBlock->inFlight = false;
     561    clearLock ++;
     562    if (memBlock->nextBlock) {
     563        MEM_ASSERT (memBlock->nextBlock->inFlight, "trying to clear an unlocked memBlock\n");
     564        memBlock->nextBlock->inFlight = false;
     565    }
     566    if (memBlock->previousBlock) {
     567        MEM_ASSERT (memBlock->previousBlock->inFlight, "trying to clear an unlocked memBlock\n");
     568        memBlock->previousBlock->inFlight = false;
     569    }
     570    MUTEX_UNLOCK(&memBlockListMutex);
     571    return;
     572# endif
     573}
    330574
    331575/* Actually allocate memory
     
    365609    // function
    366610    memBlock->func = func;
     611
     612    // per-block lock (off by default)
     613    memBlock->inFlight = false;
    367614
    368615    #if defined(PS_MEM_BACKTRACE) && defined(HAVE_BACKTRACE)
     
    394641    memBlock->refCounter = 1;                   // one user so far
    395642
     643    psMemBlock *last0 = lastMemBlockAllocated;
     644    int flight0 = (lastMemBlockAllocated) ? lastMemBlockAllocated->inFlight : 10;
     645
    396646    // need exclusive access of the memory block list now...
    397647    // this lock is very frequently called in a given program
    398     MUTEX_LOCK(&memBlockListMutex);
    399 
    400     // increment the memory id only after we've grabbed the memBlockListMutex
     648    // lastMemBlockAllocated is always true except the first allocation
     649    BLOCKLAST_LOCK ();
     650
     651    psMemBlock *last1 = lastMemBlockAllocated;
     652    int flight1 = (lastMemBlockAllocated) ? lastMemBlockAllocated->inFlight : 10;
     653
     654    if (false) {
     655        fprintf (stderr, "last 0 : %lld, last 1 : %lld, flight0: %d, flight1: %d\n", (long long int) last0, (long long int) last1, (int) flight0, (int) flight1);
     656    }
     657
     658    // increment the memory id only after we've grabbed the memBlockListMutex this value is
     659    // returned by psMemGetID and psMemGetLastID, but only ever modified here
    401660    *(psMemId* )&memBlock->id = ++memid;
    402661
     
    415674    }
    416675
    417     MUTEX_UNLOCK(&memBlockListMutex);
     676    BLOCK_UNLOCK (memBlock->nextBlock, true);
    418677
    419678    // And return the user the memory that they allocated
     
    536795    // Reallocate the memory
    537796
    538     // we need to lock this whole section because we need to be able to adjust
    539     // lastMemBlockAllocated if needed (ie, this IS lastMemBlockAllocated)
    540     // this lock is frequently called in a given program
    541     MUTEX_LOCK(&memBlockListMutex);
     797    /* psRealloc and locks:
     798
     799       psRealloc calls 'realloc' to change the size of the memory block.  This call is likely
     800       to change the address of the memory block itself.  This is a problem because the address
     801       of the memory block is referred to by the neighbor blocks via their memBlock->nextBlock
     802       and memBlock->previousBlock elements.  If the current memBlock is the first one, then
     803       this issue also applies to 'lastMemBlockAllocated'.  The elements may also be modified
     804       by psFree and psAlloc.  Thus, we need to prevent multiple threads from calling psFree,
     805       psAlloc, or psRealloc on neighbor memory blocks at the same time.
     806
     807       Unfortunately, unlike psAlloc and psFree, psRealloc cannot perform the (expensive)
     808       system call operation (realloc) first and adjust the pointers in separate step (since
     809       the value modified by realloc *is* one of those points.  It is thus necessary to include
     810       the realloc call within the locked segement, making psRealloc likely to serialize the
     811       thread operations.
     812
     813       Alternatively, we can recognize that the lock only need be applied to operations which
     814       are performed on neighboring memBlocks.  We can thus reduce the contention by having a
     815       per-memBlock boolean (inFlight) which says the memBlock or a neighbor is being
     816       modified.  If psFree and psAlloc respect that boolean, they will not modify a memBlock
     817       which is already being realloced (or its neighbor).  In this way, the 'realloc' call can
     818       be performed outside of the locked region.
     819
     820    */
     821
     822    // set a lock, then lock this memblock and its neighbors.  at the end of this call,
     823    // the global mutex is released, but the memBlock and its neighbors are protected
     824    BLOCKSET_LOCK (memBlock, false);
     825    // MUTEX_LOCK(&memBlockListMutex);
    542826
    543827    psMemBlock *nextBlock = memBlock->nextBlock;
     
    549833    bool isBlockLast = (memBlock == lastMemBlockAllocated);
    550834
     835    // do the expensive system call.  other threads can continue to modify other memBlocks
     836    // except this one and its two neighbors
    551837    memBlock = (psMemBlock *)realloc(memBlock, sizeof(psMemBlock) + size + sizeof(void *));
    552838    if (memBlock == NULL) {
     
    582868    }
    583869
    584     MUTEX_UNLOCK(&memBlockListMutex);
     870    // all of the list modifications are done; set the lock and clear the per-memBlock locks
     871    BLOCKSET_UNLOCK(memBlock, false);
     872    // MUTEX_UNLOCK(&memBlockListMutex);
     873
     874    // XXX these are not actually guaranteed : another thread may already grab them before we get here
     875    // psAssert (!memBlock->inFlight, "unreleased lock?");
     876    // psAssert (!nextBlock || !nextBlock->inFlight, "unreleased lock?");
     877    // psAssert (!previousBlock || !previousBlock->inFlight, "unreleased lock?");
    585878
    586879    return memBlock + 1;                    // usr memory
     
    601894    psS32 j = 0;
    602895    psMemBlock *topBlock = lastMemBlockAllocated;
     896
     897    // XXX move this elsewhere?
     898    fprintf (stderr, "set %d locks, cleared %d locks, retry on %d locks (memID %ld)\n", setLock, clearLock, retryLock, memid);
    603899
    604900    // make sure that the memblock list is free of corruption before we crawl
     
    8081104    // if we have multiple references, just decrement the count and return.
    8091105    // we need a MUTEX_LOCK here: otherwise, two functions can race on refCounter--;
    810     MUTEX_LOCK(&memBlockListMutex);
     1106    BLOCK_LOCK (memBlock, true);
    8111107    if (memBlock->refCounter > 1) {
    8121108        memBlock->refCounter--;
     
    8161112            memFreeID += memFreeCallback(memBlock);
    8171113        }
    818         MUTEX_UNLOCK(&memBlockListMutex);
     1114        BLOCK_UNLOCK(memBlock, true);
    8191115        return ptr;
    8201116    }
    821     MUTEX_UNLOCK(&memBlockListMutex);
     1117    BLOCK_UNLOCK(memBlock, true);
    8221118
    8231119    // we can't invoke freeFunc() while we're holding memBlockListMutex as it
     
    8281124
    8291125    // this lock is frequently set in a given program
    830     MUTEX_LOCK(&memBlockListMutex);
     1126    BLOCKSET_LOCK (memBlock, true);
    8311127
    8321128    // Did the user ask to be informed about this deallocation?
     
    8491145    }
    8501146
     1147    BLOCKSET_UNLOCK (memBlock, true);
     1148
     1149    // XXX keep this?
     1150    psAssert (memBlock->nextBlock == nextBlock, "unexpected rearrangement");
     1151    psAssert (memBlock->previousBlock == previousBlock, "unexpected rearrangement");
     1152
    8511153    // NULL out the refs so no one can get confused
    8521154    memBlock->nextBlock = NULL;
    8531155    memBlock->previousBlock = NULL;
    8541156
    855     MUTEX_UNLOCK(&memBlockListMutex);
     1157    // XXX these are not actually guaranteed : another thread may already grab them before we get here
     1158    // psAssert (!memBlock->inFlight, "unreleased lock?");
     1159    // psAssert (!nextBlock || !nextBlock->inFlight, "unreleased lock?");
     1160    // psAssert (!previousBlock || !previousBlock->inFlight, "unreleased lock?");
    8561161
    8571162    // invoke free only after we've released the block list lock as free()
Note: See TracChangeset for help on using the changeset viewer.