BOOL IsPrime (int n) { static int nLast = 1; static BOOL fLastIsPrime = FALSE; // if the parameter value has not changed since last time, // use the finished result if (n == nLast) return fLastIsPrime; // calculate and remember the new result nLast = n; fLastIsPrime = slow_IsPrime (n); return fLastIsPrime; }
slow_IsPrime()
call, the other thread that calls IsPrime()
will find the values ​​of the nLast
and fLastIsPrime
inappropriate for each other.nLast
= 5, fLastIsPrime
= TRUE, and two threads simultaneously call IsPrime(5)
, then they have absolutely no need to line up: nothing prevents them from simultaneously using the cached value.#define IsLocked (l) ((l) & 1) BOOL IsPrime (int n) { static int nLast = 1; static BOOL fLastIsPrime = FALSE; static LONG lCounter = 0; // try to take the value from the cache LONG lCounterStart = InterlockedReadAcquire (& lCounter, -1); if (! IsLocked (lCounterStart) && n == nLast) { BOOL fResult = fLastIsPrime; // nobody touched the cache behind our back? if (InterlockedReadRelease (& lCounter, -1) == lCounterStart) return fResult; } // either the reading from the cache failed, or the value did not fit: // calculated in the usual way BOOL fIsPrime = slow_IsPrime (n); // try to save the value in the cache lCounterStart = lCounter; if (! IsLocked (lCounterStart) && InterlockedCompareExchangeAcquire (& lCounter, lCounterStart + 1, lCounterStart) == lCounterStart) { nLast = n; fLastIsPrime = fIsPrime; InterlockedCompareExchangeRelease (& lCounter, lCounterStart + 2, lCounterStart + 1); } return fIsPrime; }
lCounter
order bit in lCounter
means that the cache is “locked” during recording; the remaining bits store the version number. This organization allows you to combine unlocking and updating the version number into one simple operation.lCounter
operation Acquire to make sure that the read values nLast
and fLastIsPrime
were written before the version number. If, judging by the number read, the cache is not blocked, then we read from it the last values ​​of the parameter and the result. If the result fits, we take it; but first you need to make sure that the version number has not changed during the time we read from the cache. If it has changed, then perhaps the values ​​we read do not match each other, so the “suitable” result is in fact unreliable.InterlockedCompareExchangeRelease
remove the lock and increase the version number. The operation must be Release, so that changes to the data are recorded in memory before the lock is released.TryEnterCriticalSection
:BOOL IsPrime (int n) { static int nLast = 1; static BOOL fLastIsPrime = FALSE; BOOL fHaveAnswer = FALSE; BOOL fIsPrime; // try to take the value from the cache if (TryEnterCriticalSection (& g_cs)) { if (n == nLast) { fHaveAnswer = TRUE; fIsPrime = fLastIsPrime; } LeaveCriticalSection (& g_cs); } if (fHaveAnswer) return fIsPrime; // either the critical section is busy or the value did not fit: // calculated in the usual way fIsPrime = slow_IsPrime (n); // try to save the value in the cache if (TryEnterCriticalSection (& g_cs)) { nLast = n; fLastIsPrime = fIsPrime; LeaveCriticalSection (& g_cs); } return fIsPrime; }
BOOL IsPrime (int n) { static int nLast = 1; static BOOL fLastIsPrime = FALSE; BOOL fHaveAnswer = FALSE; BOOL fIsPrime; // try to take the value from the cache if (TryAcquireSRWLockShared (& g_lock)) { if (n == nLast) { fHaveAnswer = TRUE; fIsPrime = fLastIsPrime; } ReleaseSRWLockShared (& g_lock); } if (fHaveAnswer) return fIsPrime; // either the cache is locked for writing, or the value did not fit: // calculated in the usual way fIsPrime = slow_IsPrime (n); // try to save the value in the cache if (TryAcquireSRWLockExclusive (& g_lock)) { nLast = n; fLastIsPrime = fIsPrime; LeaveSRWLockExclusive (& g_lock); } return fIsPrime; }
IsPrime()
is called several times with one value (for example, 13), and then many times in a row with another value (for example, 17) - then the head of the streams that check if the result is not cached for 17 simply won't let any stream through to cache the result for 17! It turns out that if the load on the cache is very high (which is exactly why we added it!), Then the implementation based on slim reader-writer locks turns the cache into an almost useless one.Source: https://habr.com/ru/post/118515/
All Articles