Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should interlocked implementations based on CompareExchange use SpinWait?

Below is an implementation of an interlocked method based on Interlocked.CompareExchange.

Is it advisable for this code to use a SpinWait spin before reiterating?

public static bool AddIfLessThan(ref int location, int value, int comparison)
{
    int currentValue;
    do
    {
        currentValue = location; // Read the current value
        if (currentValue >= comparison) return false; // If "less than comparison" is NOT satisfied, return false
    }
    // Set to currentValue+value, iff still on currentValue; reiterate if not assigned
    while (Interlocked.CompareExchange(ref location, currentValue + value, currentValue) != currentValue);
    return true; // Assigned, so return true
}

I have seen SpinWait used in this scenario, but my theory is that it should be unnecessary. After all, the loop only contains a handful of instructions, and there is always one thread making progress.

Say that two threads are racing to perform this method, and the first thread succeeds right away, whereas the second thread initially makes no change and has to reiterate. With no other contenders, is it at all possible for the second thread to fail on its second attempt?

If the example's second thread cannot fail on the second attempt, then what might we gain with a SpinWait? Shaving off a few cycles in the unlikely event that a hundred threads are racing to perform the method?

like image 318
Timo Avatar asked Nov 04 '19 15:11

Timo


2 Answers

My non-expert opinion is that in this particular case, where two threads occasionally call AddIfLessThan, a SpinWait is unneeded. It could be beneficial in case the two threads were both calling AddIfLessThan in a tight loop, so that each thread could make progress uninterrupted for some μsec.

Actually I made an experiment and measured the performance of one thread calling AddIfLessThan in a tight loop versus two threads. The two threads need almost four times more to make the same number of loops (cumulatively). Adding a SpinWait to the mix makes the two threads only slightly slower than the single thread.

like image 78
Theodor Zoulias Avatar answered Nov 09 '22 07:11

Theodor Zoulias


Two threads is just not a subject for SpinWait discussion. But this code doesn't tell us how many threads can actually compete for the resource and with relatively high number of threads using of the SpinWait can become beneficial. In particular with higher number of threads the virtual queue of threads, which are trying to successfully acquire the resource, gets longer and those threads which happen to be served in the end have good chances to exceed their time slice allocated by scheduler which in turn can lead to higher CPU consumption and may affect execution of other scheduled threads even with higher priority. The SpinWait has good answer to this situation by setting some upper limit of allowed spins after which the context switching is going to be executed. So it's a reasonable trade-off between necessity to make an expensive system call in order to trigger a context switching and uncontrolled user mode CPU consumption which is under the risk to impact the other threads execution in certain situations.

like image 32
Dmytro Mukalov Avatar answered Nov 09 '22 06:11

Dmytro Mukalov