Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prevent compiler/cpu instruction reordering c#

Tags:

I have an Int64 containing two Int32 like this:

[StructLayout(LayoutKind.Explicit)]
public struct PackedInt64
{
    [FieldOffset(0)]
    public Int64 All;
    [FieldOffset(0)]
    public Int32 First;
    [FieldOffset(4)]
    public Int32 Second;
}

Now I want constructors (for all, first and second). However the struct requires all fields to be assigned before the constructor is exited. Consider the all constructor.

public PackedInt64(Int64 all)
{
    this.First = 0;
    this.Second = 0;
    Thread.MemoryBarrier();
    this.All = all;
}

I want to be absolutely sure that this.All is assigned last in the constructor so that half of the field or more isn't overwritten in case of some compiler optimization or instruction reordering in the cpu.

Is Thread.MemoryBarrier() sufficient? Is it the best option?

like image 421
Carl R Avatar asked Jul 07 '13 21:07

Carl R


People also ask

How do I stop compiler reordering?

To prevent compiler reorderings at other times, you must use a compiler-specific barrier. GCC uses __asm__ __volatile__("":::"memory"); for this purpose. This is different from CPU reordering, a.k.a. the memory-ordering model.

Why compiler reorder instructions?

Compiler and hardware try to reorder programs in order to improve their efficiency, while respecting dependencies. Indeed their actions is complementary. Compiler can consider larger reorganisations than processor and uses more complex heuristics to do it.

Can compiler reorder function calls?

If you mean that the difference can not be observed, then yes, the compiler (and even the CPU itself) is free to reorder the operations.


2 Answers

Yes, this is the correct and best way of preventing reordering.

By executing Thread.MemoryBarrier() in your sample code, the processor will never be allowed to reorder instructions in such a way that the access/modification to First or Second will occur after the access/modification to All. Since they both occupy the same address space, you don't have to worry about your later changes being overwritten by your earlier ones.

Note that Thread.MemoryBarrier() only works for the current executing thread -- it isn't a type of lock. However, given that this code is running in a constructor and no other thread can yet have access to this data, this should be perfectly fine. If you do need cross-thread guarantee of operations, however, you'll need to use a locking mechanism to guarantee exclusive access.

Note that you may not actually need this instruction on x86 based machines, but I would still recommend the code in case you run on another platform one day (such as IA64). See the below chart for what platforms will reorder memory post-save, rather than just post-load.

enter image description here

like image 114
David Pfeffer Avatar answered Sep 29 '22 16:09

David Pfeffer


The MemoryBarrier will prevent re-ordering, but this code is still broken.

LayoutKind.Explicit and FieldOffsetAttribute are documented as affecting the memory layout of the object when it is passed to unmanaged code. It can be used to interop with a C union, but it cannot be used to emulate a C union.

Even if it currently acts the way you expect, on the platform you tested, there is no guarantee that it will continue to do so. The only guarantee made is in the context of interop with unmanaged code (that is, p/invoke, COM interop, or C++/CLI it-just-works).

If you want to read a subset of bytes in a portable future-proof manner, you'll have to use bitwise operations or a byte array and BitConverter. Even if the syntax isn't as nice.

like image 23
Ben Voigt Avatar answered Sep 29 '22 15:09

Ben Voigt