Блокируют ли инструкции сохранения последующие инструкции при промахе кеша? - PullRequest
5 голосов
/ 17 июня 2020

Допустим, у нас есть процессор с двумя ядрами (C0 и C1) и строка кэша, начинающаяся с адреса k, которая изначально принадлежит C0. Если C1 выдает команду сохранения в 8-байтовом слоте в строке k, повлияет ли это на пропускную способность следующих инструкций, которые выполняются на C1?

В руководстве по оптимизации Intel есть следующий параграф

Когда инструкция записывает данные в ячейку памяти [...], процессор гарантирует, что у него есть строка, содержащая это место в памяти. находится в его кэше L1d [...]. Если строка кэша отсутствует, выполняется выборка со следующих уровней с помощью запроса RFO [...] RFO, и сохранение данных происходит после удаления инструкции. Таким образом, задержка хранилища обычно не влияет на саму инструкцию хранилища.

Со ссылкой на следующий код:

// core c0
foo();
line(k)->at(i)->store(kConstant, std::memory_order_release);
bar();
baz();

Цитата из руководства Intel заставляет меня предположить, что в код выше, выполнение кода будет выглядеть так, как если бы хранилище было по существу бездействующим, и не повлияло бы на задержку между концом foo() и началом bar(). Напротив, для следующего кода

// core c0
foo();
bar(line(k)->at(i)->load(std::memory_order_acquire));
baz();

Задержка между концом foo() и началом bar() будет зависеть от нагрузки, так как следующий код имеет результат загрузки как зависимость.


Этот вопрос в основном касается того, как процессоры Intel (в семействе Broadwell или новее) работают в приведенном выше случае. Кроме того, в частности, как код C ++, который выглядит так, как показано выше, компилируется до сборки для этих процессоров.

1 Ответ

6 голосов
/ 17 июня 2020

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

Например:

foo()
*x = y;
bar()

Если foo() не изменяет x или y, а bar не загружается из *x, хранилище является независимым и может начать выполнение даже до завершения foo() (или даже до его запуска), а bar() может выполняться до того, как хранилище зафиксируется в кеше, а bar() может даже выполняться, пока foo() работает, et c.

Хотя существует небольшое прямое влияние, это не означает, что нет косвенных воздействий, и действительно, хранилище может доминировать над временем выполнения.

Если хранилище пропускает в кеше, это может ie использовать ресурсы вне ядра, пока пропуск кэша удовлетворяется. Это также обычно предотвращает истощение последующих хранилищ, что может быть узким местом: если буфер хранилища заполняется, интерфейс полностью блокируется и новые инструкции больше не попадают в планировщик.

Наконец, все зависит от деталей окружающего кода, как обычно. Если эта последовательность выполняется повторно, а foo() и bar() короткие, промахи, связанные с хранилищем, могут доминировать во время выполнения. В конце концов, буферизация не может скрыть стоимость неограниченного количества хранилищ. В какой-то момент вы будете привязаны к внутренней c пропускной способности магазинов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...