In a project of mine, I noticed the server spiking in CPU usage as the number of clients connected increase.
10 clients: 0% mostly, spikes to 7% randomly.
15 clients: 0% mostly, spikes to 10% randomly.
25 clients: 10% mostly, spikes to 60% randomly.
50 clients: 50% mostly, spikes to 60%, CPU is overall at 100% (due to gameservers).
(Note: there are 8 logical cores on the CPU)
I narrowed down the problem to Thread.Yield, on this line: https://github.com/vercas/vProto/blob/master/vProto/Base%20Client/Package%20Sending.cs#L121
As soon as I comment that line out, CPU usage stays at 0% constantly even with 100 clients!
Why is Thread.Yield doing this?
I don't know why this usage of Thread.Yield/Sleep1 might cause these spikes, however I refute that it is caused merely by "context switching". (I've no doubt it relates, but a stronger explanation is required.)
Thread.Sleep or Thread.Yield seems to give a satisfactory answer for when Yield and Sleep are used exclusively - basically that Yield, like Sleep(0), might not yield - although it may not directly apply the case of "Yield and Sleep if required"1 vs "always Sleep without trying to yield Yield", as presented in this question.
1The original CPU-spiking code presented used: if (!Thread.Yield()) Thread.Sleep(10);. (This is an example of why it is important to include relevant code in questions.)
My arguments against context switching being the problem follow.
Windows use preemptive scheduling and context switches dozens of times per second, even when threads don't actively yield.
Thread.Sleep(x), where x > 0, will always cause a context-switch; yet Thread.Sleep(1) is reported to not cause such spikes.
Thread.Yield might not cause a context switch, yet it is reported to cause spikes.
The operating system (read: Thread.Yield) will not switch execution if..
It is due to the way Thread.Yield releases processing. It forces the current process thread to release prematurely. This in turn sends out messages to all other processes telling them to do their own thing. Switching process context is expensive in terms of swapping out memory, loading cached processes, and moving through the process list out of sequence.
From MSDN:
If this method succeeds, the rest of the thread's current time slice is yielded. The operating system schedules the calling thread for another time slice, according to its priority and the status of other threads that are available to run.
Yielding is limited to the processor that is executing the calling thread. The operating system will not switch execution to another processor, even if that processor is idle or is running a thread of lower priority. If there are no other threads that are ready to execute on the current processor, the operating system does not yield execution, and this method returns false.
This method is equivalent to using platform invoke to call the native Win32 SwitchToThread function. You should call the Yield method instead of using platform invoke, because platform invoke bypasses any custom threading behavior the host has requested.
UPDATE
There has been some challenge to the statement that Thread.Yield causes expensive context switching. Here are additional references:
Difference between Thread.Sleep0 and Thread.Yield
Threading in C# - Joseph Albahari
MSDN - About Processes and Threads
MSDN - Multitasking Considerations
The recommended guideline is to use as few threads as possible, thereby minimizing the use of system resources. This improves performance. Multitasking has resource requirements and potential conflicts to be considered when designing your application. The resource requirements are as follows:
- The system consumes memory for the context information required by both processes and threads. Therefore, the number of processes and threads that can be created is limited by available memory.
- Keeping track of a large number of threads consumes significant processor time. If there are too many threads, most of them will not be able to make significant progress. If most of the current threads are in one process, threads in other processes are scheduled less frequently.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With