8

I am a C++ habitat working on a C# project.
I have encountered the following situation.

I have class MyClass and want to avoid any 2 objects of type MyClass ever to share a cache line even if I have an array or any sequential collection of type MyClass.
In C++ we can declare class alignas(hardware_destructive_interference_size) Myclass and this will make sure that any 2 objects never share a cache line.

Is there any equivalent method in C#?

Eduard Rostomyan
  • 7,050
  • 2
  • 37
  • 76
  • 3
    I think the bigger question is *why* you want something like that? What is the underlying problem that's supposed to solve? Right now this is really an [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem), please ask about the underlying problem directly instead. – Some programmer dude Feb 25 '22 at 21:46
  • @Someprogrammerdude, suppose I have an array of MyClass objects, N objects and N threads each opiating on each MyClass object. I don't want them to share the cache line because that will cause false sharing and threads will start playing ping-pong with the cache line and will significantly affect performance. The solution is to make sure there is at least cache line size distance between 2 consecutive objects. – Eduard Rostomyan Feb 25 '22 at 21:51
  • 4
    You would split the collection into chunks and then give each thread a chunk of objects to work with. Also you really don't have to worry about stuff like this when working with C#. If you need that last bit of extra performance then C# is the wrong tech. – Charles Feb 25 '22 at 21:53
  • 1
    For 4-24 objects you have you can just pad the actual data on both sides with unused 64byte-64k structs as fields to make sure the interesting part is on its own cache line... (Side note: it is an interesting case where one does non-trivial operations on data that is so small requires constant updates - for most regular PCs your N should be <16 as you presumably run CPU-only tasks, so for 16 objects you can also randomly allocate 1000 of unused objects relying on randomness to give plenty of space between actually used once) – Alexei Levenkov Feb 25 '22 at 22:56
  • 1
    You have of course measured that this is an actual top-two bottleneck in your program? – Some programmer dude Feb 26 '22 at 09:28
  • 2
    @Someprogrammerdude the question is not about what I do, what I measured and what is my bottleneck. Its plain simple, is there a way to achieve this, or not. If you know a way please share with me, if not then don't turn this question into performance optimization tutorial. – Eduard Rostomyan Feb 26 '22 at 11:49
  • I don't think you can align, but can control layout using StructLayout: https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.structlayoutattribute?redirectedfrom=MSDN&view=net-6.0, which can help w/ false sharing within a large struct. Think best can do is ensure they are larger than a cache line, explicitly adding padding field(s) if needed. – Chris Kushnir Feb 28 '22 at 05:43
  • 1
    .NET is called "managed" for a reason (and in fact the generated code is usually very optimized you can have a rapid check of the resulting asm using this tool here https://sharplab.io/). If you want super fine grained control, you should use another framework, more native – Simon Mourier Mar 05 '22 at 18:33

3 Answers3

7

No, you can't control the alignment or memory location of classes (reference types). You can't even get the size of a class instance in memory.

It is possible to control the size and alignment of structs (and of the fields within them). Structs are value types and work pretty much the same as in C++. If you create an array of a struct, each entry has the size of the struct, and if that is large enough, you could get what you want. But there's no guarantee that the individual entries are really distributed over cache lines. That will also depend on the size and organisation of the cache.

Note also that the address of a managed instance (whether a class or a struct) can change at runtime. The garbage collector is allowed to move instances around to compact the heap, and it will do this quite often. So there is also no guarantee that the same instance will always end up in the same cache line. It is possible to "pin" an instance while a certain block executes, but this is mostly intended when interfacing with native functions and not in a context of performance optimization.

PMF
  • 14,535
  • 3
  • 23
  • 49
2

From reading this page, it looks to me like you can use the following - for structs only though, not classes (my example would be for Intel CPUs):

[StructLayout(LayoutKind.Sequential, Pack=64)]

I can't see a guarantee anywhere that an object defined in this way will actually be allocated on (or indeed remain on) a 64 byte boundary, but this construct would not be of much use if it wasn't. If you want to check (and I personally most certainly would), there are some suggestions about how to get the address of an allocated object (until it moves!) here:

Memory address of an object in C#

I also can't see any way of not hard-coding the cache line size, but if you're always going to be running your code on Intel CPUs then that shouldn't be a problem, and I think (most) ARM chips also use 64 bytes, although not, apparently, Apple's M1 (huh!, typical).

Of course, if you do this you need to align the corresponding C++ class / struct accordingly. I think it would be wise to hard-code the alignment as 64 bytes there too, don't you? :)

Paul Sanders
  • 24,133
  • 4
  • 26
  • 48
0

aligned_alloc rounding the size up to the nearest cacheline.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
  • What is the nearest cache line? – Eduard Rostomyan Mar 01 '22 at 16:46
  • I meant the next closest multiple of cacheline size (I just get the device's cacheline size from intrinsics but I'm sure there is a better way). – MaxCreatesCode Mar 01 '22 at 17:06
  • Can you give an example what you mean? I can't seem to find `aligned_alloc` anywhere in the c# documentation. Are you sure you're not talking about C++? – PMF Mar 05 '22 at 09:49
  • Assuming you have access to [alignedalloc](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.nativememory.alignedalloc) which calls aligned_alloc but I had only written some C++ to do something similar. Unfortunately still probably not the best solution to the original question & was only a related side note on aligned_alloc's implementation since other answers were claiming this was not possible. – MaxCreatesCode Mar 10 '22 at 16:51