#include
#include
#include
#include
#include
//make this global for easy access
LONG volatile lockValue = 1;
const UINT64 MIN_SPIN = 250;
const UINT64 MAX_SPIN = 1000000;
UINT64 globalSpins = 0;
BOOL GetLock(
__in LONG Id) // I Thread id and additional information
{
assert(Id != 0);
UINT64 oldId = InterlockedCompareExchangeAcquire(&lockValue, Id, 0);
return oldId == 0;
}
void ReleaseLock( void *)
{
//sleep an arbitrarily hard-coded time
Sleep(5000);
//set the value to 0 so the lock is released and spinlock acquires the lock
lockValue = 0;
}
void
SpinToAcquireLockWithExponentialBackoff(__in DWORD Id, __out UINT32 * BackoffCount)
{
UINT32 Backoffs = 0;
UINT64 spinLimit = MIN_SPIN;
UINT64 iSpin = 0;
while (true)
{
assert(spinLimit <= MAX_SPIN && spinLimit >= MIN_SPIN);
// Spin for a while without reading the lock.
//
for (iSpin = 0; iSpin < spinLimit; iSpin++)
{
_mm_pause();
}
//printf_s("iSpin = %ld\n", iSpin);
//increment the global spins
globalSpins = globalSpins + iSpin;
//printf_s("globalSpins = %ld\n", globalSpins);
// Try to get the lock.
if (GetLock(Id))
{
break;
}
//
spinLimit = min(MAX_SPIN, spinLimit * 2);
// See if we need to backoff
//
Backoffs++;
}
//output the final backoffs
*BackoffCount = Backoffs;
} // SpinToAcquireLockWithExponentialBackoff
void ExerciseSpinLockCode()
{
UINT32 backoffCount = 0;
LARGE_INTEGER beforeHighRes, afterHighRes, elapsedMicroseconds, frequency;
//spawn a thread to simulate lock release
HANDLE hThread = (HANDLE)_beginthread(ReleaseLock, 0, NULL);
//get start times
QueryPerformanceCounter(&beforeHighRes);
long int before = GetTickCount();
//invoke Spinlock code
SpinToAcquireLockWithExponentialBackoff(GetCurrentThreadId(), &backoffCount);
//get the end times
long int after = GetTickCount();
QueryPerformanceCounter(&afterHighRes);
QueryPerformanceFrequency(&frequency);
//calculate microseconds
//ticks divided by ticks-per-second (frequency) , gives us seconds
//converted to microseconds by multiplying to 1 mil
elapsedMicroseconds.QuadPart = (afterHighRes.QuadPart - beforeHighRes.QuadPart);
elapsedMicroseconds.QuadPart *= 1000000;
elapsedMicroseconds.QuadPart /= frequency.QuadPart;
UINT64 SpinsPerMillisecond = (globalSpins) / (after - before);
printf_s("SpinToAcquireLockWithExponentialBackoff: Milliseconds elapsed = %ld, Spins=%I64d, Backoffs=%ld\n", after - before, globalSpins, backoffCount);
printf_s("SpinToAcquireLockWithExponentialBackoff: Spins/Millisecond=%I64d\n", SpinsPerMillisecond);
printf_s("SpinToAcquireLockWithExponentialBackoff: Spins/Millisecond(QPC)=%I64d\n", globalSpins/(elapsedMicroseconds.QuadPart/1000));
//wait on the thread completion
WaitForSingleObject(hThread, INFINITE);
}
void ExerciseSimpleLoopCode()
{
long i=0;
LARGE_INTEGER beforeHighRes, afterHighRes, elapsedMicroseconds, frequency;
QueryPerformanceCounter(&beforeHighRes);
//loop, no pause
for (i; i < MAX_SPIN*10; i++)
{
;
}
QueryPerformanceCounter(&afterHighRes);
QueryPerformanceFrequency(&frequency);
//https://docs.microsoft.com/en-us/windows/desktop/SysInfo/acquiring-high-resolution-time-stamps
elapsedMicroseconds.QuadPart = (afterHighRes.QuadPart - beforeHighRes.QuadPart);
elapsedMicroseconds.QuadPart *= 1000000;
elapsedMicroseconds.QuadPart /= frequency.QuadPart;
long elapsedTimeQPC = elapsedMicroseconds.QuadPart / 1000 == 0 ? 1 : (elapsedMicroseconds.QuadPart / 1000);
printf_s("Simple Loop: Millisecond elapsed (QPC)=%lld, Loops=%d\n", (elapsedMicroseconds.QuadPart / 1000), i);
printf_s("Simple Loop: Spins/Millisecond (QPC)=%ld\n", i / elapsedTimeQPC);
}
int main()
{
ExerciseSpinLockCode();
ExerciseSimpleLoopCode();
printf_s("Press ENTER to end...");
_gettchar();
return 0;
}
use tempdb
go
declare @cntr int = 0
if exists(select name from tempdb.sys.sysobjects where name like '#_spin_waits%')
drop table #_spin_waits
create table #_spin_waits
(
lock_name varchar(128)
,collisions bigint
,spins bigint
,sleep_time bigint
,backoffs bigint
,snap_time datetime
)
while (@cntr < 4)
begin
--capture the current stats
insert into #_spin_waits
(
lock_name
,collisions
,spins
,sleep_time
,backoffs
,snap_time
)
select name
,collisions
,spins
,sleep_time
,backoffs
,getdate()
from sys.dm_os_spinlock_stats
waitfor delay '00:00:10'
set @cntr = @cntr + 1
end
--Analysis
declare @cpu int
select @cpu = cpu_count from sys.dm_os_sys_info
--SPINLOCKS compute delta
select t2.[lock_name] as spinlock_name,
cast(cast(t2.spins as float) - cast(t1.spins as float) as bigint) delta_spins,
cast (cast(t2.Backoffs as float) - cast (t1.Backoffs as float) as bigint) delta_backoff,
DATEDIFF(MILLISECOND,t1.snap_time,t2.snap_time) delta_minuntes,
cast(cast(t2.spins as float) - cast(t1.spins as float) as bigint)/DATEDIFF(MILLISECOND,t1.snap_time,t2.snap_time)/@cpu delta_spins_per_millisecond_per_CPU
from
(select row_number () over ( partition by [lock_name] order by snap_time) row, * from [#_spin_waits]
where snap_time in
(select min(snap_time) from #_spin_waits) ) t1
join
(select row_number () over ( partition by [lock_name] order by snap_time) row, * from [#_spin_waits]
where snap_time in
(select max(snap_time) from #_spin_waits) ) t2
on t1.row = t2.row and t1.[lock_name]=t2.[lock_name]
where cast(cast(t2.spins as float) - cast(t1.spins as float) as bigint) > 0
order by delta_spins desc
Enjoy!
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.