Как писать или читать память, не касаясь кеша

Есть ли способ записать/прочитать память, не касаясь кеша L1/L2/L3 под процессорами x86?

И кеш в x86-процессорах полностью управляется аппаратным обеспечением?

EDIT: Я хочу сделать это, потому что хочу пробовать скорость памяти и видеть, ухудшается ли какая-либо часть производительности памяти.

Ответ 1

CPU действительно управляет своими собственными кэшами на оборудовании, но x86 предоставляет вам некоторые способы повлиять на это управление.

Чтобы получить доступ к памяти без кеширования, вы можете:

  • Используйте не временные инструкции x86, они предназначены для того, чтобы сообщить CPU, что вы не будете повторно использовать эти данные снова, поэтому нет смысла удерживать его в кеше. Эти инструкции в x86 обычно называются movnt * (с суффиксом в соответствии с типом данных, например, movnti для загрузки нормальных целых чисел в регистры общего назначения). Существуют также инструкции по потоковым нагрузкам/хранилищам, которые также используют подобный метод, но более подходят для потоков с высоким BW (когда вы загружаете полные строки последовательно). Чтобы использовать их, либо кодируйте их во встроенной сборке, либо используйте встроенные функции, предоставляемые вашим компилятором, большинство из них вызывает это семейство _mm_stream _ *

  • Измените тип памяти для конкретной области на необработанную. Поскольку вы заявили, что не хотите отключать все кеширование (и это действительно так, поскольку это также будет включать в себя код, стек, карту страниц и т.д.), Вы можете определить конкретный регион, в котором ваш тестовый набор данных находится в виде нечитаемого, используя MTRR (регистры диапазона типа памяти). Есть несколько способов сделать это, вам нужно будет прочитать для этого некоторую документацию.

  • Последняя опция - это обычная выборка, что означает, что она сначала кэшируется, а затем вынуждает ее очищать все уровни кеша с помощью специальной команды clflush (или полного wbinvd, если вы хотите сбросить весь кеш). Удостоверьтесь, что вы правильно выполняете эти операции, чтобы вы могли гарантировать, что они сделаны (и, конечно же, не измеряют их как часть задержки).

Сказав это, если вы хотите сделать все это только во время чтения вашей памяти, вы можете получить плохие результаты, так как большинство процессоров обрабатывают невременные или некогерентные обращения "неэффективно". Если вы сразу же после форсирования чтения поступают из памяти, это лучше всего достигается путем манипулирования кэшами LRU путем последовательного доступа к набору данных, который достаточно велик, чтобы не вписываться в какой-либо кеш. Это приведет к тому, что большинство схем LRU (не все!) Сначала отбросят самые старые строки, поэтому в следующий раз, когда вы оберните их, они должны будут появиться из памяти.

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

Ответ 2

Leor preety много перечисляет наиболее " pro" решения для вашей задачи. Я попытаюсь добавить к этому еще одно предложение, которое может достичь тех же результатов и может быть написано на простом C с помощью простого кода. Идея заключается в создании ядра, похожего на "Глобальный случайный доступ", найденного в тесте HPCC Challenge.

Идея ядра заключается в скачкообразно перемещаться через огромный массив значений 8B, который является общим размером вашей физической памяти (так что если у вас 16 ГБ ОЗУ, вам нужно Массив 8 ГБ, ведущий к элементам 1G 8B). Для каждого прыжка вы можете читать, писать или RMW в целевом местоположении.

Это, скорее всего, измеряет задержку памяти, поскольку случайное сканирование через RAM делает кеширование очень неэффективным. Вы получите чрезвычайно низкие коэффициенты попадания в кеш, и если вы сделаете достаточные операции над массивом, вы сможете измерить фактическую производительность памяти. Этот метод также делает предварительную выборку очень неэффективной, поскольку нет обнаруженного шаблона.

Вам нужно учитывать следующие вещи:

  • Убедитесь, что компилятор не оптимизирует ваш цикл ядра (обязательно сделайте что-нибудь в этом массиве или сделайте что-то со значениями, которые вы читаете на нем).
  • Используйте очень простой генератор случайных чисел и не храните целевые адреса в другом массиве (который будет кэшироваться). Я использовал линейный конгруэнтный генератор. Таким образом, следующий адрес вычисляется очень быстро и не добавляет дополнительных задержек, кроме тех, что находятся в ОЗУ.