Вы не отметили свою ОС, но давайте предположим, что вы используете Linux.Этот материал будет другим в другой ОС (и, возможно, даже в различных вариантах одной и той же ОС).
При доступе на чтение к не отображенной странице обработчик ошибок страницы ядра отображается в общесистемной общей нулевой странице., с правами только для чтения.
Это объясняет столбцы LoadInit-1U|K
: даже если ваша инициализирующая нагрузка превышает виртуальную область 64 МБ, выполняющую нагрузки, только один физический Страница 4K, заполненная нулями, сопоставляется, поэтому вы получаете примерно ноль пропусков кэша после первых 4КБ, которые округляются до нуля после нормализации. 1
При доступе на запись вНеотображенная страница или общая нулевая страница только для чтения, ядро отобразит новую уникальную страницу от имени процесса.Эта новая страница гарантированно обнуляется, поэтому, если в ядре не осталось каких-то известных страниц, которые можно обнулить, это потребует обнуления страницы (фактически memset(new_page, 0, 4096)
) до ее отображения.
Это в значительной степениобъясняет остальные столбцы, кроме StoreInit-2U|K
.В этих случаях, даже если кажется, что пользовательская программа выполняет все хранилища, ядро в конечном итоге выполняет всю тяжелую работу (за исключением одного хранилища на страницу), поскольку при сбое пользовательского процесса на каждой странице ядро записывает нулик этому, у которого есть побочный эффект переноса всех страниц в кэш L1.Когда обработчик ошибок возвращается, хранилище запуска и все последующие хранилища для этой страницы попадут в кэш L1.
Это все еще не полностью объясняет StoreInit-2.Как поясняется в комментариях, столбец K фактически включает в себя количество пользователей, что объясняет этот столбец (вычитание количества пользователей оставляет его примерно равным нулю для каждого события, как и ожидалось).Оставшаяся путаница заключается в том, почему L2_RQSTS.ALL_RFO
- это не 1, а какое-то меньшее значение, например 0,53 или 0,68.Возможно, событие недооценивается, или мы пропускаем какой-то микроархитектурный эффект, например, тип предварительной выборки, который предотвращает RFO (например, если линия загружается в L1 с помощью некоторого типа операции загрузки до сохраненияRFO не произойдет).Вы можете попробовать включить другие L2_RQSTS
события, чтобы увидеть, появляются ли отсутствующие события.
Вариации
Это не должно быть так во всех системах.Конечно, другие ОС могут иметь разные стратегии, но даже Linux на x86 может вести себя по-разному в зависимости от различных факторов.
Например, вместо нулевой страницы 4K, вы можете получить огромную нулевую страницу 2 MiB ..Это изменило бы тест, так как 2 MiB не умещается в L1, поэтому тесты LoadInit, вероятно, покажут пропуски в пользовательском пространстве на первом и втором циклах.
В целом, если вы использовали огромные страницы,степень детализации ошибки страницы будет изменена с 4 КиБ до 2 МиБ, что означает, что только небольшая часть обнуленной страницы останется в L1 и L2, так что вы получите пропуски L1 и L2, как вы и ожидали.Если ваше ядро когда-либо реализует отказ для анонимных отображений (или любого используемого сопоставления), это может иметь аналогичный эффект.
Другая возможность состоит в том, что ядро может обнулять страницы вфон и так готовы к нулю страниц.Это позволило бы удалить K-показатели из тестов, поскольку обнуление не происходит во время ошибки страницы, и, вероятно, добавило бы ожидаемые пропуски к счетчикам пользователя.Я не уверен, что ядро Linux когда-либо делало это или имеет возможность сделать это, но было патчей, плавающих вокруг .Другие ОС, такие как BSD, сделали это.
RFO Prefetchers
О «предварительных сборщиках RFO» - предварительные сборщики RFO на самом деле не являются предварительными сборщиками в обычном смысле, и они не связаны с предварительными выборщиками L1D, которые можно отключить.Насколько я знаю, «предварительная выборка RFO» из L1D просто относится к отправке запроса RFO для хранилищ в буфере хранилища, которые достигают начала буфера хранилища.Очевидно, что когда хранилище попадает в начало буфера, пора отправлять RFO, и вы бы не назвали это предварительной выборкой, но почему бы не отправлять некоторые запросы и на второе хранилище из головы, и так далее?Это предварительные выборки RFO, но они отличаются от обычной предварительной выборки тем, что ядро знает адрес, который был запрошен: это не предположение.
Там равно предположение в том смысле, что получение дополнительных строк, отличных от текущего заголовка, может быть потрачено впустую, если другое ядро отправляет RFO для этой линии до того, как ядро получит возможность писать с него: в этом случае запрос был бесполезным и просто увеличил трафик когерентности,Таким образом, существуют предикторы, которые могут уменьшить эту предварительную выборку буфера хранилища, если она происходит слишком часто.Также могут быть предположения в том смысле, что предварительная выборка из буфера хранилища может отправлять запросы для младших хранилищ, которые не удалились, за счет бесполезного запроса, если хранилище окажется на неверном пути.На самом деле я не уверен, что нынешние реализации делают это.
1 Это поведение на самом деле зависит от деталей кэша L1: текущие реализации Intel VIPT допускают несколько виртуальных псевдонимовОдна и та же линия, чтобы все жили счастливо в L1.Текущие реализации AMD Zen используют другую реализацию (микротэги), которая не позволяет L1 логически содержать несколько виртуальных псевдонимов, поэтому я ожидаю, что Zen пропустит L2 в этом случае.