Внеочередное выполнение сохраняет иллюзию запуска в программном порядке для одного потока / ядра .Это похоже на правило оптимизации «как если бы» в C / C ++: делайте все, что хотите, внутренне, пока видимые эффекты одинаковы.
Отдельные потоки могут взаимодействовать друг с другом только через память, поэтому глобальный порядок памятиОперации (загрузка / хранение) - это единственный видимый снаружи побочный эффект выполнения 1 .
Даже для процессоров, работающих в обычном порядке, операции памяти могут стать глобально неупорядоченными.(например, даже простой конвейер RISC с буфером хранилища будет переупорядочивать StoreLoad, как x86).Процессор, который запускает загрузку / хранение по порядку, но позволяет им завершать работу не по порядку (чтобы скрыть задержку пропуска кэша), также может переупорядочивать нагрузки, если он специально не избегает этого (или, как современный x86, выполнять агрессивно вне очереди).упорядочивать, но делать вид, что не делает этого, тщательно отслеживая упорядочение памяти).
Простой пример: две цепочки зависимостей ALU могут перекрываться
(связанный: http://blog.stuffedcow.net/2013/05/measuring-rob-capacity/ для получения дополнительной информации о том, насколько велико окно для нахождения параллелизма на уровне инструкций, например, если вы увеличите это значение до times 200
, вы увидите только ограниченное перекрытие. Также связано: ответ от начинающего до промежуточного уровняЯ написал о том, как ООП-процессор, такой как Haswell или Skylake, находит и использует ILP.
Более глубокий анализ влияния lfence
здесь, см. Понимание влияния lfence нацикл с двумя длинными цепочками зависимостей, для увеличения длины
global _start
_start:
mov ecx, 10000000
.loop:
times 25 imul eax,eax ; expands to imul eax,eax / imul eax,eax / ...
; lfence
times 25 imul edx,edx
; lfence
dec ecx
jnz .loop
xor edi,edi
mov eax,231
syscall ; sys_exit_group(0)
, встроенный (с nasm
+ ld
) в статический исполняемый файл на Linux x86-64, запускается (on Skylake) в ожидаемых 750M тактовых циклах для каждой цепочки 25 * 10M
imul инструкций, умноженных на 3 такта.
Комментирование одной из imul
цепочек не меняет время, необходимое для выполнения: все еще750M циклов.
Это определенное доказательство неупорядоченного выполнения, чередуя две цепочки зависимостей, в противном случае.(imul
пропускная способность равна 1 за такт, задержка 3 такта. http://agner.org/optimize/. Таким образом, третья цепочка зависимостей могла бы быть смешана без особого замедления).
Фактические числа из taskset -c 3 ocperf.py stat --no-big-num -etask-clock,context-switches,cpu-migrations,page-faults,cycles:u,branches:u,instructions:u,uops_issued.any:u,uops_executed.thread:u,uops_retired.retire_slots:u -r3 ./imul
:
- с обеими цепями imul:
750566384 +- 0.1%
- только с цепочкой EAX:
750704275 +- 0.0%
- с одной цепью
times 50 imul eax,eax
: 1501010762 +- 0.0%
(почти в два раза больше, чеммедленно, как и ожидалось). - с
lfence
, предотвращая перекрытие между каждым блоком 25 imul
: 1688869394 +- 0.0%
, хуже, чем в два раза медленнее.uops_issued_any
и uops_retired_retire_slots
- 63M, вместо 51M, в то время как uops_executed_thread
по-прежнему 51M (lfence
не использует никаких портов выполнения, но, очевидно, две lfence
инструкции стоят 6 мопов в слитых доменах. AgnerТуман измеряется только 2.)
(lfence
сериализует выполнение команды , но не в памяти).Если вы не используете загрузку NT из памяти WC (что не произойдет случайно), это не будет, кроме как остановить более поздние инструкции от выполнения до тех пор, пока предыдущие инструкции не будут «выполнены локально».то есть, пока они не удалились из ядра, вышедшего из строя.Вероятно, поэтому он более чем удваивает общее время: ему приходится ждать, пока последние imul
в блоке пройдут больше этапов конвейера.)
lfence
на Intel всегда так, но на AMD, он только частично сериализован с включенным смягчением спектра .
Сноска 1 : Существуют также побочные каналы синхронизации, когда два логических потока совместно используют один физическийнить (гиперпоточность или другое SMT).например, выполнение последовательности независимых imul
инструкций будет выполняться по 1 разу в такт на недавнем процессоре Intel, если другой гиперпотоке не нужен порт 1 для чего-либо.Таким образом, вы можете измерить, насколько сильно давление порта 0, синхронизируя цикл ALU на одном логическом ядре.
Другие микроархитектурные побочные каналы, такие как доступ к кэшу, более надежны.Например, Spectre / Meltdown проще всего использовать с побочным каналом чтения кэша, а не ALU.
Но все эти побочные каналы являются привередливыми и ненадежными по сравнению с архитектурно-поддерживаемыми операциями чтения / записи в общую память, поэтому они важны только для безопасности.Они не используются намеренно в одной и той же программе для связи между потоками.
MFENCE на Skylake - это барьер OoO exec, такой как LFENCE
mfence
на неожиданных блоках Skylakeвыполнение не по порядку imul
, например, lfence
, хотя это не задокументировано.(См. Обсуждение в чате).
xchg [rdi], ebx
(неявный префикс lock
) вообще не блокирует неупорядоченное выполнение инструкций ALU.Общее время по-прежнему составляет 750 миллионов циклов при замене lfence
на xchg
или инструкции lock
ed в вышеприведенном тесте.
Но при mfence
стоимость возрастает до 1500 миллионов циклов + времяза 2 mfence
инструкции.Чтобы провести контролируемый эксперимент, я оставил счетчик команд таким же, но переместил инструкции mfence
рядом друг с другом, чтобы цепочки imul
могли переупорядочиваться друг с другом, и время уменьшилось до 750M + время для 2mfence
инструкции.
Такое поведение Skylake, скорее всего, является результатом исправления обновления микрокода ошибка SKL079 , MOVNTDQA из памяти WC может пройти раньше Инструкции MFENCE .Наличие опечатки показывает, что раньше было возможно выполнить более поздние инструкции до завершения mfence
, поэтому, вероятно, они сделали грубое исправление, добавив lfence
моп в микрокод для mfence
.
Это еще один фактор в пользу использования xchg
для хранилищ seq-cst или даже lock add
для некоторой стековой памяти в качестве автономного барьера. Linux уже выполняет обе эти функции, но компиляторывсе еще используйте mfence
для барьеров.См. Почему хранилище std :: atomic с последовательной последовательностью использует XCHG?
(См. Также обсуждение выбора барьеров для Linux в этой ветке групп Google со ссылками на3 отдельные рекомендации по использованию lock addl $0, -4(%esp/rsp)
вместо mfence
в качестве автономного барьера.