4

I have looked here in StackOverflow for information implementing Core Affinity for a Thread,
in .NET.

Some answers say that .NET does not support it for its own (managed) threads,
and only supports it for the non-managed threads running on the operating system.

On the other hand, other answers, mention the following properties:
- ProcessThread.IdealProcessor link
- ProcessThread.ProcessorAffinity link

As can be seen, those 2 properties are not properties of the Thread class, but of the ProcessThread class.

So I would like to ask:
If someone is creating a .NET application,
and wants to set the Core affinity for the Threads of his application,
is it safe and supported to do that, on the .NET Managed Threads?

(If yes, then I wonder why those 2 properties are exposed on the ProcessThread class
and not on the Thread class?)

PS: I am using .NET Framework v3.5 and v2.0,
and not the newer versions of the Framework.

spaceman
  • 1,061
  • 1
  • 11
  • 31

2 Answers2

7

This is a more detailed answer that some people may find it useful.

If you want to do that exclusively using APIs of an existing .NET Standard, then the answer is indeed "no", and I'll explain why. I'll also discuss how to achieve that using a set of .NET Standard 2.0 APIs and either a single OS API or a single .NET Core 2.1 API.

It's true that, in general, a fixed one-to-one mapping between managed threads and native threads is not guaranteed throughout the lifetime of a managed application. This is based on the CLI standard Section I.12.3.1:

The CLI manages multiple concurrent threads of control (not necessarily the same as the threads provided by a host operating system), multiple managed heaps, and a shared memory address space.

One thing that is not stated very clearly is whether the collection of native threads that are used to run managed threads are all contained within the same process or multiple processes. But there are statements in the CLI standard and the .NET documentation that indicate that the whole managed application lives within a single OS process. Also I'm not aware of any implementation that schedules managed threads on multiple OS processes.

Let's first consider a simple case where there is only one managed thread and/or only one native thread. This case can be easily handled by setting the affinity of the whole process using the Process.ProcessorAffinity property. Irrespective of how that single managed thread is mapped to multiple native threads or that single native thread is mapped to multiple manged threads, there can only be a single affinity value for the whole managed application.

Otherwise, there can be multiple affinities. A native thread of type ProcessThread offers the write-only property ProcessorAffinity. The managed thread type Thread offers no such API. However, it offers the BeginThreadAffinity static method that allows the current managed thread to fix its mapping to whatever native thread it's currently mapped to until the managed thread calls EndThreadAffinity. Note that BeginThreadAffinity is not a hint to the runtime. Either an exception is thrown or it returns successfully with a fixed mapping for the current managed thread. Now if we could get the current native thread, we can just change its processor affinity using ProcessThread.ProcessorAffinity. Unfortunately, in contrast to getting the current managed thread, there is no standard managed API that returns a reference to or the identifier of the current native thread. As you can see, we can achieve a fixed mapping using only .NET Standard 2.0 APIs, but there is no way to figure out which native thread is mapped to which managed thread. I don't think there is a good technical reason why not to have such an API.

One way to proceed is to call some OS-dependent API to get the ID of the current native thread. On Windows, this is GetCurrentThreadId from kernel32.dll. The current managed thread can call this API to get the ID of the current native thread. Then, a reference to the corresponding ProcessThread object can be obtained by iterating over the native threads using Process.GetCurrentProcess().Threads and finding the one with the matching ID. After that, ProcessThread.ProcessorAffinity can be used to effectively set the affinity of the current managed thread. Note that since ProcessThread.ProcessorAffinity is a write-only property, there is no .NET Standard 2.0 API that enables you to restore the old affinity. I don't know why it's write-only.

Another, much more complicated, way is by using Thread.GetCurrentProcessorId, which currently exists only in .NET Core 2.1 (latest version). You can set the affinity of each native thread to a specific processor and check which managed thread is currently running on that processor. Eventually, you can determine which managed thread is mapped to which native thread.

Although it's possible to achieve a fixed mapping as discussed above, it seems to me that BeginThreadAffinity does not guarantee that each managed thread is mapped to a unique native thread. So a one-to-one mapping cannot be achieved in a standard-compliant way.

In .NET Core, ProcessThread.ProcessorAffinity is only implemented on Windows. On other OSs, it throws a PlatformNotSupportedException.

Hadi Brais
  • 22,259
  • 3
  • 54
  • 95
  • Hi Hadi.. Thank you for the detailed answer. Just from the amount of details, it can be seen how "unsupported" it is, and how fragile it would be to play with it. BTW I am using .NET Framework v3.5 and v2.0, I never moved to the newer ones since they became too bloated, IMO.. – spaceman Jun 06 '18 at 07:29
  • @spaceman The `BeginThreadAffinity` is supported and a good use case if you need to interact with native code that relies on thread affinity - that's definitely one thing I missed. I assumed you're talking about managed-only code. It should still be kept to a minimum, though. – Luaan Jun 06 '18 at 07:43
  • Luaan: I was talking about managed-only code.. Like I wrote in the question details: "f someone is creating a .NET application, and wants to set the Core affinity for the Threads of his application". I do not wish to interact with non-managed threads... – spaceman Jun 06 '18 at 09:02
  • @spaceman It seems to me that all of these APIs mentioned in my answer have been designed specifically to be used elsewhere in the .NET framework itself rather than by .NET application programmers. Otherwise, the way they are designed does not make much sense to me. – Hadi Brais Jun 06 '18 at 15:43
  • Hadi: Indeed. For this reason, I have abandoned the idea, and see the answer for my quetion here as No... Thank you both for clarifying this once and for all, I am sure this will be useful for other people who need a sharp answer regarding this. – spaceman Jun 06 '18 at 16:58
5

No.

.NET threads do not map 1:1 to operating system threads. Thread affinity is for operating system threads. Since the runtime can switch .NET threads between OS threads at will, setting processor affinity will do nothing at best, and waste performance in a typical multi-threaded scenario.

Note that ProcessThread simply contains information about running operating system thread in a process. It's just what you get when you ask what threads a process has - it's part of the OS, not .NET. In contrast, Thread is about your threads, and only the managed ones.

Luaan
  • 62,244
  • 7
  • 97
  • 116
  • So I should give up on the idea to set core affinity for my threads in .NET? How sad that .NET does not enable that.. .NET is so rich in terms of multi-threading options and settings, yet this option is for some reason forgotton/neglected.. – spaceman Jun 05 '18 at 13:30
  • @spaceman It doesn't fit the philosophy. Even allowing custom thread priorities was a mistake IMO. The problem is that you're second-guessing the thread scheduler, which has a lot more information about the system load, power management, switching cost, NUMA etc. Use cases where you could actually benefit from setting the affinity are extremely rare (and don't forget that GC wouldn't respect it anyway), while misusing thread affinities and priorities is extremely common and leads to severe performance and power efficiency degradation. – Luaan Jun 05 '18 at 18:10
  • What do you mean GC wouldn't respect it anyway? – Hadi Brais Jun 06 '18 at 02:52
  • @HadiBrais Well, you use thread affinity to ensure workloads are distributed across execution cores in a fixed way. This is usually to give some threads priority, improve cache efficiency or reduce latency on a critical thread. But when the GC kicks in, it destroys all of these potential benefits - it will stop all the managed threads, use the CPU fully for its operation, pretty much throws out all the cache lines (since it needs to walk the managed heap) and any latency gains you might have had are far offset by this. If GC is fine for you, affinity shouldn't make a difference either. – Luaan Jun 06 '18 at 05:27
  • @Luaan Yes, but this is not a GC specific issue. The affinity does not mean that only that thread can use the core(s), it just means that if and when the thread gets scheduled to run, it should only be scheduled on those core(s). Any of the cores can still run other threads any time the OS scheduler wants. – Hadi Brais Jun 06 '18 at 05:34
  • @HadiBrais Of course. I did show examples of why thread affinity is more often detrimental than not - the GC simply makes the remainder even less likely to be worthwhile. My implication was that if you need thread affinity for performance (and not just as a work-around e.g. synchronization issues), you're probably using the wrong tool anyway - using an unmanaged language or even a real-time OS might be more appropriate; thread affinity doesn't help and will likely hurt instead. – Luaan Jun 06 '18 at 07:41