x86-64 использование LFENCE
Я пытаюсь понять, как правильно использовать заборы при измерении времени с помощью RDTSC/RDTSCP. На несколько вопросов по СО, связанных с этим, уже дан подробный ответ. Я прошел через несколько из них. Я также прочитал эту действительно полезную статью на ту же тему: http://www.intel.com/content/dam/www/public/us/en/documents/white-papers/ia-32-ia-64-benchmark-code-execution-paper.pdf
Тем не менее, в другом онлайн-блоге есть пример использования LFENCE вместо CPUID на x86. Мне было интересно, как LFENCE предотвращает загрязнение измерений RDTSC в более ранних магазинах. Например
<Instr A>
LFENCE/CPUID
RDTSC
<Code to be benchmarked>
LFENCE/CPUID
RDTSC
В приведенном выше случае LFENCE гарантирует, что все более ранние загрузки завершаются до этого (поскольку SDM говорит: инструкции LFENCE не могут проходить более ранние чтения.). Но как насчет более ранних магазинов (скажем, Instr A был магазином)? Я понимаю, почему CPUID работает, потому что это инструкция сериализации, а LFENCE - нет.
Одно объяснение, которое я нашел, было в Intel SDM VOL 3A Раздел 8.3, следующее сноска:
LFENCE предоставляет некоторые гарантии при заказе команд. Он не выполняется до тех пор, пока все предыдущие инструкции не будут выполнены локально, и никакие более поздние инструкции не начнут выполняться, пока не завершится LFENCE.
По сути, LFENCE действует как MFENCE. В таком случае, зачем нам две отдельные инструкции LFENCE и MFENCE?
Я, наверное, что-то упустил.
Заранее спасибо.
2 ответа
Ключевым моментом является локальное наречие в цитируемом предложении "Оно не выполняется до тех пор, пока все предыдущие инструкции не будут выполнены локально".
Я не смог найти четкое определение "завершить локально" весь комплект руководства Intel, мои предположения объяснены ниже.
Для локального выполнения инструкции необходимо, чтобы ее выходные данные были вычислены и были доступны для других команд далее в ее цепочке зависимостей. Кроме того, любой побочный эффект этой инструкции должен быть виден внутри ядра.
Для выполнения в глобальном масштабе инструкция должна иметь свои побочные эффекты, видимые для других компонентов системы (например, для других процессоров).
Если мы не определяем вид "полноты", мы говорим об этом, как правило, означает, что это не имеет значения, или это подразумевается в контексте.
Для многих инструкций, выполняемых локально и глобально, это одно и то же.
Например, для загрузки, чтобы выполнить ее локально, некоторые данные должны быть извлечены из памяти или кэшей. Это то же самое, что и глобальное завершение, поскольку мы не можем пометить загрузку как завершенную, если не будем сначала читать из иерархии памяти.
Однако для магазина ситуация иная.
Процессоры Intel имеют Store Buffer для обработки операций записи в память из главы 11.10 руководства 3:
Процессоры Intel 64 и IA-32 временно сохраняют каждую запись (сохранение) в память в буфере хранения. Буфер хранения повышает производительность процессора, позволяя процессору продолжать выполнять инструкции, не ожидая завершения записи в память и / или в кэш. Это также позволяет задерживать запись для более эффективного использования циклов шины доступа к памяти.
Таким образом, хранилище можно завершить локально, поместив в буфер хранилища, с точки зрения ядра запись выглядит так, как будто она прошла весь путь до памяти.
Загрузка из того же ядра хранилища при определенных обстоятельствах может даже считывать это значение (это называется пересылкой хранилища).
Однако для завершения в глобальном масштабе магазин должен быть опустошен из буфера магазина.
Наконец, необходимо добавить, что буфер хранилища истощается с помощью инструкций сериализации:
Содержимое буфера хранилища всегда выгружается в память в следующих ситуациях:
• (Только для P6 и более поздних семейств процессоров) При выполнении команды сериализации.
• (только Pentium III и более поздние семейства процессоров) При использовании инструкции SFENCE для заказа магазинов.
• (Только для Pentium 4 и более новых семейств процессоров) При использовании инструкции MFENCE для заказа магазинов.
Закончив с введением, посмотрим, что lfence
, mfence
а также sfence
делать:
LFENCE не выполняется до тех пор, пока все предыдущие инструкции не будут выполнены локально, и никакие более поздние инструкции не начнут выполняться, пока LFENCE не завершится.
MFENCE выполняет сериализацию всех инструкций загрузки из памяти и сохранения в память, которые были выполнены до инструкции MFENCE. MFENCE не сериализует поток команд.
SFENCE выполняет сериализацию всех инструкций хранения в память, которые были выпущены до инструкции SFENCE.
Так lfence
Это более слабая форма сериализации, которая не истощает Store Buffer, так как она эффективно сериализует инструкции локально, все загрузки перед тем, как они должны быть завершены до его завершения.
sfence
только сериализует хранилища, в основном процесс не позволяет выполнить хранилище до sfence
в отставке. Это также истощает буфер Store.
mfence
это не простая комбинация двух, потому что это не сериализация в классическом смысле, это sfence
это также препятствует выполнению будущих загрузок.
Это может ничего не стоить, что sfence
был представлен первым, а другие двое пришли позже, чтобы добиться более детального контроля над упорядочением памяти.
Наконец, я был использован, чтобы закрыть rdtsc
инструкция между двумя lfence
инструкции, чтобы быть уверенным, что никакое изменение порядка "назад" и "вперед" было невозможно.
Однако я уверен в правильности этой техники.
Как вы справедливо заметили, это вопрос сериализации. По вашему вопросу
Зачем нам нужны две отдельные инструкции LFENCE и MFENCE?
ответ в Intel SDM в разделе "5.6.4 - Инструкции по управлению кэшированием и SSE2":
LFENCE Сериализует операции загрузки
MFENCE сериализует операции загрузки и хранения
Так LFENCE
вероятно, используется потому, что MFENCE
не нужно для RDTSC
,