Есть ли разница между (rdts c + lfence + rdts c) и (rdts c + rdtscp) временем выполнения измерений? - PullRequest
3 голосов
/ 16 января 2020

Насколько я знаю, основное различие в порядке выполнения во время выполнения в процессоре относительно команд rdts c и rdtscp заключается в том, ожидает ли выполнение, пока все предыдущие инструкции не будут выполнены локально.

Другими словами, это означает lfence + rdts c = rdtscp, потому что lfence, предшествующий инструкции rdts c, заставляет следующую rdts c выполняться после всех предыдущих инструкций, заканчивающихся sh локально.

Однако я видел пример кода, который использует rdts c в начале измерения и rdtscp в конце. Есть ли разница между использованием двух rdts c и rdts c + rdtscp?

    lfence
    rdtsc
    lfence
    ...
    ...
    ...
    lfence
    rdtsc
    lfence
    lfence
    rdtsc
    lfence
    ...
    ...
    ...
    rdtscp
    lfence

1 Ответ

5 голосов
/ 16 января 2020

TL; DR

rdtscp и lfence/rdtsc имеют одинаковые свойства последовательной восходящей сериализации на процессорах Intel. На процессорах AMD с сериализацией диспетчеризации lfence обе последовательности также имеют одинаковые свойства сериализации в восходящем направлении. Что касается более поздних инструкций, rdtsc в последовательности lfence/rdtsc может отправляться для выполнения одновременно с более поздними инструкциями. Такое поведение может быть нежелательным, если вы также хотите точно рассчитать время этих последующих инструкций. Как правило, это не является проблемой, поскольку планировщик станции резервирования отдает приоритет более старым мопам для отправки, если нет структурных опасностей. После того, как lfence выйдет на пенсию, rdtsc мопов будут самыми старыми в РС, вероятно, без структурных опасностей, поэтому они будут немедленно отправлены (возможно, вместе с некоторыми более поздними мопами). Вы также можете поставить lfence после rdtsc.

В руководстве Intel V2 говорится о rdtscp (выделено мое) следующее:

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

Часть «операция чтения» здесь относится к чтение счетчика меток времени. Это говорит о том, что rdtscp внутренне работает как lfence с последующим rdtsc + чтением IA32_TSC_AUX. То есть сначала выполняется lfence, затем выполняются две операции чтения из регистров (возможно, одновременно).

На большинстве процессоров Intel и AMD, поддерживающих эти инструкции, lfence/rdtsc имеют немного больший размер количество мопов, чем rdtscp. Число lfence мопов, упомянутое в таблицах Агнера , относится к случаю, когда инструкции lfence выполняются вплотную, что создает впечатление, что lfence декодируется в меньшее число моп (1 или 2), чем то, во что фактически декодируется один lfence (5 или 6 моп). Обычно lfence используется без других последовательных lfence с. Вот почему lfence/rdtsc содержит больше мопов, чем rdtscp. Таблицы Агнера также показывают, что на некоторых процессорах rdtsc и rdtscp имеют одинаковое количество мопов, что, я не уверен, является правильным. rdtscp имеет больше смысла иметь один или несколько мопов, чем rdtsc. Тем не менее, задержка может быть важнее, чем разница в количестве мопов, потому что именно это напрямую влияет на накладные расходы при измерении.

С точки зрения переносимости, rdtsc старше rdtscp; rdtsc впервые был поддержан на процессорах Pentium, в то время как первые процессоры, поддерживающие rdtscp, были выпущены в 2005-2006 годах (см .: Что такое тип процессора g cc, который включает поддержку RDTSCP? ). Но большинство используемых сегодня процессоров Intel и AMD поддерживают rdtscp. Другое измерение для сравнения между двумя последовательностями состоит в том, что rdtscp загрязняет еще один регистр (то есть ECX), чем rdtsc.

В итоге, если вам не важно читать IA32_TSC_AUX MSR, нет особой причины, по которой вы должны выбирать одно из другого. Я бы использовал rdtscp и отступил бы до lfence/rdtsc (или lfence/rdtsc/lfence) на процессорах, которые его не поддерживают. Если вам нужна максимальная точность синхронизации, используйте метод, описанный в Измерение задержки памяти со счетчиком отметок времени .


Как Андреас Абель указал , вам все еще нужно lfence после последнего rdtsc(p), поскольку он не упорядочен по последующим инструкциям:

lfence                    lfence
rdtsc      -- ALLOWED --> B
B                         rdtsc

rdtscp     -- ALLOWED --> B
B                         rdtscp

Это также указано в руководствах .


Что касается использования rdtscp, мне кажется правильным считать его компактным lfence + rdtsc.
В руководствах используется различная терминология для двух инструкций (например, "выполнено локально" по сравнению с "глобально видимым" для нагрузок). ) но описанное поведение кажется таким же.
Я предполагаю, что так в оставшейся части этого ответа.

Однако rdtscp - это одна инструкция, а lfence + rdtscp - две, что делает часть lfence в профилированном коде.
Да, lfence должен быть легковесным с точки зрения ресурсов выполнения бэкэнда (это только маркер) он все еще занимает ресурсы переднего плана (два мопа?) и слот в ROB.
rdtscp декодируется в большее количество мопов из-за его способности читать IA32_TSC_AUX, так что пока он сохраняет ресурсы переднего плана (часть), занимает больше внутреннего.
Если чтение TS C выполняется сначала (или одновременно) с идентификатором процессора, то этот дополнительный моп актуален только для последующего кода .
Это может быть причиной того, что он используется в конце, но не в начале теста (где дополнительные мопы повлияют на код). Этого достаточно, чтобы сместить / усложнить некоторые микроархитектурные тесты.

Вы не можете избежать lfence после и rdtsc(p), но вы можете избежать одного до с rdtscp.
Это кажется ненужным для первого rdtsc, так как предыдущий lfence все равно не профилируется.


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

Программное обеспечение пользовательского режима может использовать RDTSCP, чтобы определить, произошла ли миграция ЦП между последовательными считываниями TS C.

Для этого, конечно, требуется чтение IA32_TSC_AUX раньше (чтобы было с чем сравнивать), поэтому перед профилирующим кодом должен быть rdpid или rdtscp.
Если кто-то может позволить себе не использовать ecx, первый rdtsc может быть rdtscp тоже (но см. выше), иначе (вместо сохранения идентификатора процессора в профилированном коде), сначала можно использовать rdpid (таким образом, имея пару rdtsc + rdtscp вокруг профилированного кода).

Это открыто для проблемы ABA , поэтому я не думаю, что у Intel есть сильная сторона в этом (если мы не ограничимся кодом, достаточно коротким, чтобы его можно было перепланировать не более одного раза).

РЕДАКТИРОВАТЬ Как указал PeterCordes, с точки зрения меры истекшего времени наличие миграции A-> B-> A не является проблемой, поскольку опорные часы такие же.


Дополнительная информация о том, почему rdtsc(p) не полностью сериализуется: Почему RDTS C не является инструкцией сериализации? .

...