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

Почему GCC не использует LOAD (без забора) и STORE + SFENCE для последовательной согласованности?

Вот четыре подхода, чтобы сделать последовательную согласованность в x86/x86_64:

  • LOAD (без забора) и STORE + MFENCE
  • LOAD (без забора) и LOCK XCHG
  • MFENCE + LOAD and STORE (без забора)
  • LOCK XADD (0) и STORE (без забора)

Как написано здесь: http://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html

C/С++ 11 Операция x86

  • Загрузка Seq_Cst: MOV (из памяти)
  • Сохранить Seq Cst: (LOCK) XCHG// альтернатива: MOV (в память), MFENCE

Примечание: существует альтернативное отображение C/С++ 11 на x86, которое вместо блокировки (или ограждения) хранилище Seq Cst блокирует/ограждает загрузку Seq Cst:

  • Загрузка Seq_Cst: LOCK XADD (0)//альтернатива: MFENCE, MOV (из памяти)
  • Сохранить Seq Cst: MOV (в память)

GCC 4.8.2 (GDB в x86_64) использует первый (1) подход для С++ 11-std:: memory_order_seq_cst, т.е. LOAD (без забора) и STORE + MFENCE:

std::atomic<int> a;
int temp = 0;
a.store(temp, std::memory_order_seq_cst);
0x4613e8  <+0x0058>         mov    0x38(%rsp),%eax
0x4613ec  <+0x005c>         mov    %eax,0x20(%rsp)
0x4613f0  <+0x0060>         mfence

Как мы знаем, это MFENCE = LFENCE + SFENCE. Тогда этот код мы можем переписать на это: LOAD(without fence) and STORE+LFENCE+SFENCE

Вопросы:

  • Почему нам не нужно использовать LFENCE здесь до LOAD, и вам нужно использовать LFENCE после STORE (потому что LFENCE имеет смысл только до LOAD!)?
  • Почему GCC не использует подход: LOAD (без забора) и STORE + SFENCE для std:: memory_order_seq_cst?
4b9b3361

Ответ 1

std::atomic<int>::store сопоставляется с внутренним компилятором __atomic_store_n. (Этот и другие свойства атомарной операции описаны здесь: Встроенные функции для атомных операций с памятью модели памяти.) Суффикс _n делает его типовым; back-end на самом деле реализует варианты для конкретных размеров в байтах. int на x86 всегда AFAIK длиной 32 бит, так что это означает, что мы ищем определение __atomic_store_4. Руководство для внутренней версии этой версии GCC говорит, что операции __atomic_store соответствуют шаблонам описания машин с именем atomic_store‌mode; режим, соответствующий 4-байтовому целому, представляет собой "SI" (описанный здесь), поэтому мы ищем что-то под названием "atomic_storesi" в описании машины x86, И это приводит нас к config/i386/sync.md, в частности, этому биту:

(define_expand "atomic_store<mode>"
  [(set (match_operand:ATOMIC 0 "memory_operand")
        (unspec:ATOMIC [(match_operand:ATOMIC 1 "register_operand")
                        (match_operand:SI 2 "const_int_operand")]
                       UNSPEC_MOVA))]
  ""
{
  enum memmodel model = (enum memmodel) (INTVAL (operands[2]) & MEMMODEL_MASK);

  if (<MODE>mode == DImode && !TARGET_64BIT)
    {
      /* For DImode on 32-bit, we can use the FPU to perform the store.  */
      /* Note that while we could perform a cmpxchg8b loop, that turns
         out to be significantly larger than this plus a barrier.  */
      emit_insn (gen_atomic_storedi_fpu
                 (operands[0], operands[1],
                  assign_386_stack_local (DImode, SLOT_TEMP)));
    }
  else
    {
      /* For seq-cst stores, when we lack MFENCE, use XCHG.  */
      if (model == MEMMODEL_SEQ_CST && !(TARGET_64BIT || TARGET_SSE2))
        {
          emit_insn (gen_atomic_exchange<mode> (gen_reg_rtx (<MODE>mode),
                                                operands[0], operands[1],
                                                operands[2]));
          DONE;
        }

      /* Otherwise use a store.  */
      emit_insn (gen_atomic_store<mode>_1 (operands[0], operands[1],
                                           operands[2]));
    }
  /* ... followed by an MFENCE, if required.  */
  if (model == MEMMODEL_SEQ_CST)
    emit_insn (gen_mem_thread_fence (operands[2]));
  DONE;
})

Не вдаваясь в подробности, основная часть этого - тело функции C, которое будет вызываться для создания низкоуровневого "RTL" промежуточное представление операции хранения атомов. Когда он вызывается кодом вашего примера, <MODE>mode != DImode, model == MEMMODEL_SEQ_CST и TARGET_SSE2 истинно, он вызывает gen_atomic_store<mode>_1, а затем gen_mem_thread_fence. Последняя функция всегда порождает mfence. (В этом файле есть код для создания sfence, но я считаю, что он используется только для явно-кодированного _mm_sfence (от <xmmintrin.h>).)

Комментарии предполагают, что кто-то думал, что MFENCE требуется в этом случае. Я пришел к выводу, что либо вы ошибаетесь, чтобы думать, что загрузочный забор не требуется, либо это пропущенная ошибка оптимизации в GCC. Это, например, ошибка в том, как вы используете компилятор.

Ответ 2

Рассмотрим следующий код:

#include <atomic>
#include <cstring>

std::atomic<int> a;
char b[64];

void seq() {
  /*
    movl    $0, a(%rip)
    mfence
  */
  int temp = 0;
  a.store(temp, std::memory_order_seq_cst);
}

void rel() {
  /*
    movl    $0, a(%rip)
   */
  int temp = 0;
  a.store(temp, std::memory_order_relaxed);
}

В отношении атомной переменной "a", seq() и rel() являются упорядоченными и атомарными в архитектуре x86, поскольку:

  • mov - это атомная инструкция
  • mov - устаревшая инструкция и Intel promises упорядоченная семантика памяти для устаревших инструкций для совместимости со старыми процессорами, которые всегда использовали упорядоченную семантику памяти.

Никакой забор не требуется для хранения постоянного значения в атомной переменной. Заборы там, потому что std:: memory_order_seq_cst подразумевает, что вся память синхронизирована, а не только память, содержащая атомную переменную.

Эффект может быть продемонстрирован с помощью следующих функций set и get:

void set(const char *s) {
  strcpy(b, s);
  int temp = 0;
  a.store(temp, std::memory_order_seq_cst);
}

const char *get() {
  int temp = 0;
  a.store(temp, std::memory_order_seq_cst);
  return b;
}

strcpy - это библиотечная функция, которая может использовать более новые инструкции sse, если они доступны во время выполнения. Поскольку инструкции sse не были доступны в старых процессорах, нет необходимости в обратной совместимости, а порядок памяти - undefined. Таким образом, результат strcpy в одном потоке может не отображаться непосредственно в других потоках.

В приведенных выше функциях set и get используется атомное значение для обеспечения синхронизации памяти, так что результат strcpy становится видимым в других потоках. Теперь заборы имеют значение, но порядок их в вызове atom:: store не имеет значения, так как заборы не нужны внутренне в atom:: store.

Ответ 3

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

SFENCE гарантирует, что все магазины перед забором завершаются перед всеми магазинами после забора. LFENCE гарантирует, что все нагрузки перед забором будут завершены до всех нагрузок после ограждения. Для нормального доступа к памяти по умолчанию предоставляются заказывающие гарантии для отдельных операций SFENCE или LFENCE. В принципе, LFENCE и SFENCE сами по себе полезны только для более слабых режимов доступа к памяти x86.

Ни LFENCE, SFENCE, ни LFENCE + SFENCE не препятствуют тому, чтобы хранилище сменяется нагрузкой. MFENCE делает.

Соответствующей ссылкой является руководство по архитектуре Intel x86.

Ответ 4

SFENCE + LFENCE не барьер StoreLoad, поэтому предпосылка вопроса неверна.

  • СУДЕБНОЕ ОБЕСПЕЧЕНИЕ может проходить (раньше) раньше загрузок. (Это просто бар StoreStore).
  • LFENCE может передавать более ранние магазины. (Нагрузки не могут пересекать его в любом направлении: барьер LoadLoad).
  • Нагрузки могут проходить SFENCE (но магазины не могут проходить LFENCE, поэтому это барьер LoadStore, а также барьер LoadLoad).

LFENCE + SFENCE не содержит ничего, что останавливает хранилище от передачи более ранней загрузки (т.е. не предотвращает загрузку от предварительно загруженных данных до того, как другие потоки загрузили наши данные, а затем сохранили что-то новое). MFENCE делает предотвращает это.

Сообщение о предложении в блоге объясняет более подробно и с диаграммами, как барьеры StoreLoad являются особыми и имеют практический пример рабочего кода, который демонстрирует переупорядочение без MFENCE. Любой, кто путается с упорядочением памяти, должен начать с этого блога.

x86 имеет сильную модель памяти, где каждый нормальный магазин имеет семантику выпуска, и каждая нормальная нагрузка приобретает семантику. Этот пост содержит подробные сведения.

LFENCE и SFENCE существуют только для использования с movnt нагрузками/хранилищами, которые слабо упорядочены, а также в обход кеша.


В случае, если эти ссылки когда-либо умирают, в моем ответе еще больше информации о другом подобном вопросе.