Что делает __asm ​​volatile ("пауза" ::: "память");делать? - PullRequest
0 голосов
/ 19 мая 2018

Я смотрю на проект C ++ с открытым исходным кодом, который имеет следующую структуру кода:

while(true) {
  // Do something work

  if(some_condition_becomes_true)
     break;

  __asm volatile ("pause" ::: "memory");
}

Что делает последнее утверждение?Я понимаю, что __asm означает, что это инструкция по сборке, и я нашел несколько сообщений об инструкции pause, в которых говорится, что поток эффективно намекает ядру на освобождение ресурсов и дает другим потокам больше ресурсов (в контексте гиперпоточности).Но что делает ::: и что делает memory

1 Ответ

0 голосов
/ 20 мая 2018

Это _mm_pause() и барьер памяти компиляции, заключенный в один оператор GNU C Extended ASM.https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html

asm("" ::: "memory") предотвращает переупорядочение операций памяти во время компиляции, например, C ++ 11 std::atomic_signal_fence(std::memory_order_seq_cst).( не atomic_thread_fence; хотя на x86 предотвращение переупорядочения во время компиляции достаточно для того, чтобы сделать это забором для получения + освобождения, потому что единственное переупорядочение во время выполнения, которое допускает x86, это StoreLoad.) См. Джеффа ПрешингаУпорядочение памяти во время компиляции article.

Если часть инструкции asm не пуста, это также означает, что эти инструкции asm будут выполняться каждый раз, когда C логически запускает эту строку источника (потому что это volatile).

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

Вы можете найти это утверждение внутри цикла вращения, написанного без C ++ 11 std :: atomic, чтобы сказать компилятору, что он долженперечитайте значение глобальной переменной .(Поскольку "memory" clobber означает, что компилятор должен предположить, что оператор asm мог изменить значение любой глобально достижимой памяти.)

Это похоже на контекст, в котором вы его нашли: some_condition_becomes_true, вероятно, включает в себячтение не atomic / non- volatile global.

Эквивалент C ++ 11 вашего цикла:

#include <atomic>
#include <immintrin.h>
std::atomic<int> flag;

void wait_for_flag(void) {
    while(flag.load(std::memory_order_seq_cst == 0) {
        _mm_pause();
    }
}

(Не совсем эквивалентно, потому что ваша версия имеетполный барьер компилятора, в то время как у меня есть только seq-cst нагрузка, так что это не полный ограждение сигнала. Но вероятно , что не нужно, и они просто использовали что-то более сильное, чем необходимо, чтобы получить эффект volatile).


Без барьера или создания flag atomic компилятор оптимизировал бы его до:

// Do something work

if(some_condition_becomes_true) {
    // empty
} else {

  while(true) {
     // Do something work
     __asm volatile ("pause" ::: );  // no memory clobber
  }
}

, то есть это было быподнимите чек на some_condition_becomes_true из цикла и не каждый раз перечитывайте глобальное значение.

...