Подтвердить что ты не робот

Запретить переупорядочение команд компилятора/процессора С#

У меня есть Int64, содержащий два Int32, как это:

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

Теперь мне нужны конструкторы (для всех, первый и второй). Однако для структуры требуется, чтобы все поля были назначены до выхода из конструктора. Рассмотрим все конструктор.

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

Я хочу быть абсолютно уверенным, что this.All назначается последним в конструкторе, так что половина поля или более не перезаписывается в случае некоторой оптимизации компилятора или переупорядочения команд в процессоре.

Является ли Thread.MemoryBarrier() достаточным? Это лучший вариант?

4b9b3361

Ответ 1

Да, это правильный и лучший способ предотвратить переупорядочение.

Выполняя Thread.MemoryBarrier() в вашем примере кода, процессору никогда не удастся изменить порядок инструкций таким образом, чтобы доступ/модификация до First или Second произошел после доступа/изменения до All, Поскольку они оба занимают одно и то же адресное пространство, вам не нужно беспокоиться о том, что ваши последующие изменения будут перезаписаны вашими более ранними.

Обратите внимание, что Thread.MemoryBarrier() работает только для текущего исполняемого потока - это не тип блокировки. Однако, учитывая, что этот код работает в конструкторе, и ни один другой поток еще не имеет доступа к этим данным, это должно быть совершенно нормально. Однако, если вам нужна кросс-потоковая гарантия операций, вам необходимо использовать механизм блокировки для обеспечения эксклюзивного доступа.

Обратите внимание, что вам действительно не нужна эта инструкция на машинах на базе x86, но я бы по-прежнему рекомендовал код в случае, если вы запустите на другой платформе в один прекрасный день (например, IA64). См. Приведенную ниже диаграмму, для каких платформ будет изменяться порядок памяти после сохранения, а не просто после загрузки.

enter image description here

Ответ 2

Отметьте раздел замечаний по следующей ссылке: http://msdn.microsoft.com/en-us/library/system.threading.thread.memorybarrier.aspx

Он говорит, что MemoryBarrier() требуется только для многопроцессорных систем со слабым порядком памяти. Таким образом, это достаточный вариант, но зависит от того, является ли это наилучшим вариантом или нет, зависит от используемой вами системы.

Ответ 3

MemoryBarrier будет препятствовать переупорядочению, но этот код все еще сломан.

LayoutKind.Explicit и FieldOffsetAttribute задокументированы как влияет на расположение памяти объекта , когда он передается неуправляемому коду. Его можно использовать для взаимодействия с C union, но он не может использоваться для эмуляции C union.

Даже если он в настоящее время действует так, как вы ожидаете, на тестируемой платформе нет гарантии, что он будет продолжать это делать. Единственная сделанная гарантия заключается в контексте взаимодействия с неуправляемым кодом (то есть p/invoke, COM-взаимодействие или С++/CLI-он-работает).

Если вы хотите прочитать подмножество байтов в переносном будущем, вам придется использовать побитовые операции или массив байтов и BitConverter. Даже если синтаксис не так хорош.

Ответ 4

Во-первых, я знаю, что этот ответ действительно не решает проблему переупорядочения, но отрицает это. Используя небезопасный код, вы можете полностью отказаться от записи First и Second.

public unsafe PackedInt64(long all) {
    fixed (PackedInt64* ptr = &this)
        *(long*) ptr = all;
}

Он не должен быть самым элегантным решением и, вероятно, не пропускает большинство политик компании в отношении управляемого кода, но он должен работать.