Windows thread scheduler is unfair?

Sometimes when I run this simple program

#include <Windows.h> DWORD WINAPI ThreadStart(LPVOID) { for (;;) { } return 0; } int _tmain() { SetPriorityClass(GetCurrentProcess(), BELOW_NORMAL_PRIORITY_CLASS); SYSTEM_INFO si; GetSystemInfo(&si); for (DWORD i = si.dwNumberOfProcessors * 2; i > 0; i--) { CloseHandle(CreateThread(NULL, 0, &ThreadStart, NULL, 0, NULL)); } Sleep(INFINITE); } 

I observe constantly unfair flow planning, for example:

Screen shot

This is not unfair at every start, but when it is, it remains unfair throughout the life of the application.

Why is this happening, and what is its way to avoid?

+6
source share
4 answers

Some options:


If (and only if) you are using 64-bit (not 32-bit) Windows 7/8 or Windows Server 2008 R2 or later, you can completely bypass the system scheduler and take care of it yourself, although this is not a trivial undertaking!

This is described here on MSDN and is called User Mode Planning (UMS) .

A partial screenshot from MSDN below - is this some kind of use for you?

enter image description here

In addition, you might have the idea to disable hyperthreading in your BIOS (if necessary), because there is some debate about how Windows distinguishes between logical and physical kernels. See here for a discussion .

There are also several functions available, such as SetThreadAffinityMask() ( MSDN here ) and SetThreadIdealProcessor() , which can help, although I personally found this a bit hit and skip. This most often happens when I embed the total bandwidth, rather than helping it.

+1
source

You will see this on a multiprocessor system whenever a thread is executed that is not part of your program. Let's say there is a system task that starts in a few seconds, scanning .NET assemblies or something like that. It will push one of your threads out of planning for multiple time slices while threads on other cores continue to work. A thread that has been knocked out will never catch up with the rest.

+1
source

This is a shot in a dark guess.

But I'm going to suggest that Hyper-Threading may slightly distort the results.

If your bios / cmos computer allows you to disable hyperthreads, as some computers do, it would be interesting to see numbers in which your code only works on real kernels.

And even if HT is the reason why it is shifting in the stream, then I still don't know why it will do it.

0
source

On my system, SetThreadAffinityMask seems to mitigate the problem. There is still some imbalance, apparently due to the fact that one core has less time than another, but it is not so strong.

Windows seems reluctant to move these threads between cores; they rarely change core after the first second or so. If the threads are distributed unevenly between the cores, the flow time reflects this.

This is the code I used to test affinity masks:

 #include <Windows.h> #include <stdio.h> DWORD WINAPI ThreadStart(LPVOID arg) { DWORD pn1, pn2; printf("Thread %u on processor %u\n", GetThreadId(GetCurrentThread()), pn1 = GetCurrentProcessorNumber()); // The problem comes back if you enable this line // SetThreadAffinityMask(GetCurrentThread(), -1); for (;;) { for (int i = 0; i < 10000; i++); pn2 = GetCurrentProcessorNumber(); if (pn2 != pn1) { pn1 = pn2; printf("Thread %u on processor %u\n", GetThreadId(GetCurrentThread()), pn1); } } return 0; } int main(int argc, char ** argv) { SetPriorityClass(GetCurrentProcess(), BELOW_NORMAL_PRIORITY_CLASS); SYSTEM_INFO si; GetSystemInfo(&si); for (DWORD i = 0; i < si.dwNumberOfProcessors; i++) { SetThreadAffinityMask(CreateThread(NULL, 0, &ThreadStart, NULL, 0, NULL), 1 << i); SetThreadAffinityMask(CreateThread(NULL, 0, &ThreadStart, NULL, 0, NULL), 1 << i); } Sleep(INFINITE); return 0; } 

This approach also seems to mitigate the problem, although perhaps not as efficiently:

 #include <Windows.h> #include <stdio.h> DWORD WINAPI ThreadStart(LPVOID arg) { for (;;); return 0; } int main(int argc, char ** argv) { SYSTEM_INFO si; GetSystemInfo(&si); for (DWORD i = si.dwNumberOfProcessors * 2; i > 0; i--) { CreateThread(NULL, 0, &ThreadStart, NULL, 0, NULL); } for (;;) { SetPriorityClass(GetCurrentProcess(), BELOW_NORMAL_PRIORITY_CLASS); Sleep(100); SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS); } return 0; } 

I suspect that we are looking at some kind of energy saving measure based on the assumption that low priority threads do not need a fair schedule. (Perhaps installing a thread or a low priority process says, "I don't care how much CPU I get.")

0
source

Source: https://habr.com/ru/post/944595/


All Articles