Как работают линии кеша?

Я понимаю, что процессор передает данные в кеш через строки кэша, которые, например, на моем процессоре Atom, вмещают примерно 64 байта за раз, независимо от размера фактических данных, которые читаются.

Мой вопрос:

Представьте себе, что вам нужно прочитать один байт из памяти, какие 64 байта будут загружены в кеш?

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

Что это?

Ответ 1

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

Современные модули памяти ПК передают 64 бита (8 байтов) за раз, в пакет из восьми передач, поэтому одна команда запускает чтение или написать полную строку кэша из памяти. (DDR1/2/3/4 SDRAM пакетный размер может быть настроен до 64B, а CPU выбирают размер пакетной передачи, чтобы соответствовать размеру его кеширования, но 64B является обычным явлением)

Как правило, если процессор не может прогнозировать доступ к памяти (и предварительно отфильтровать его), процесс извлечения может занять ~ 90 наносекунд или ~ 250 тактов (от процессора, зная адрес для приема ЦП данные).

В отличие от этого, в кеше L1 имеется латентность загрузки 3 или 4 цикла, а перезагрузка магазина имеет задержку пересылки магазина 4 или 5 циклов на современных процессорах x86. На других архитектурах аналогичны.

Дополнительная литература: Ульрих Дреппер Что каждый программист должен знать о памяти. Совет по предварительной выборке немного устарел: современные превенторы HW более интеллектуальны, а гиперпоточность лучше, чем в P4 дней (так что поток предварительной выборки обычно является отходами). Кроме того, теги wiki имеет много для этой архитектуры.

Ответ 2

Если строки кэша имеют ширину 64 байта, то они соответствуют блокам памяти, которые начинаются с адресов, которые делятся на 64. Наименее значимые 6 бит любого адреса являются смещением в строке кэша.

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

Хотя это делается с помощью аппаратного обеспечения, мы можем показать вычисления, используя некоторые макроопределения ссылок C:

#define CACHE_BLOCK_BITS 6
#define CACHE_BLOCK_SIZE (1U << CACHE_BLOCK_BITS)  /* 64 */
#define CACHE_BLOCK_MASK (CACHE_BLOCK_SIZE - 1)    /* 63, 0x3F */

/* Which byte offset in its cache block does this address reference? */
#define CACHE_BLOCK_OFFSET(ADDR) ((ADDR) & CACHE_BLOCK_MASK)

/* Address of 64 byte block brought into the cache when ADDR accessed */
#define CACHE_BLOCK_ALIGNED_ADDR(ADDR) ((ADDR) & ~CACHE_BLOCK_MASK)

Ответ 3

Прежде всего, доступ к основной памяти очень дорог. В настоящее время процессор 2 ГГц (самый медленный один раз) имеет 2G тиков (циклов) в секунду. ЦП (виртуальное ядро ​​в настоящее время) может получать значение из своих регистров один раз за отметку. Поскольку виртуальное ядро ​​состоит из нескольких блоков обработки (ALU - арифметический логический блок, FPU и т.д.), Он может фактически обрабатывать некоторые команды параллельно, если это возможно.

Доступ к основной памяти стоит около 70 нс до 100 нс (DDR4 немного быстрее). На этот раз в основном просматривается кеш L1, L2 и L3, а затем попадает в память (команда отправки на контроллер памяти, который отправляет его в банки памяти), дождаться ответа и сделать это.

100 нс означает около 200 тиков. Таким образом, в основном, если программа всегда пропускает кеши, доступ к которой каждый доступ к памяти, процессор будет тратить около 99,5% своего времени (если он только считывает память) в режиме ожидания ожидания памяти.

Чтобы ускорить работу, есть кеши L1, L2, L3. Они используют память, находящуюся непосредственно на микросхеме, и используют различные транзисторные схемы для хранения данных бит. Это занимает больше места, больше энергии и является более дорогостоящим, чем основная память, поскольку процессор обычно создается с использованием более совершенной технологии, а сбой производства в памяти L1, L2, L3 имеет шанс сделать CPU бесполезным (дефектом), поэтому большие кеши L1, L2, L3 увеличивают частоту ошибок, которая снижает доходность, которая непосредственно снижает ROI. Таким образом, существует огромный компромисс, когда дело доходит до размера доступного кеша.

(в настоящее время создается больше кешей L1, L2, L3, чтобы иметь возможность деактивировать определенные порции, чтобы уменьшить вероятность того, что фактический производственный дефект является областью кеш-памяти, что делает дефект ЦП в целом).

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

  • Кэш L1:1ns до 2ns (2-4 цикла)
  • Кэш L2: от 3 нс до 5 нс (6-10 циклов)
  • Кэш L3: от 12 нс до 20 нс (24-40 циклов)
  • ОЗУ: 60 нс (120 циклов)

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

Таким образом, кеш существенно ускоряет доступ к памяти (60ns против 1ns).

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

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

Но рядом с обычной копией памяти был распространен другой последовательный доступ к памяти. Примером является анализ серии информации. Наличие массива целых чисел и вычисление суммы, среднего, среднего или даже более простого поиска определенного значения (фильтра/поиска) были еще одним очень важным классом алгоритмов, выполняемых каждый раз на любом CPU общего назначения.

Таким образом, анализируя шаблон доступа к памяти, было очевидно, что данные читаются последовательно очень часто. Была высокая вероятность того, что если программа читает значение в индексе i, что программа также будет считывать значение я + 1. Эта вероятность немного выше вероятности того, что одна и та же программа также будет считывать значение я + 2 и так далее.

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

Доступ к памяти в режиме повышения означает, что адрес отправляется, и несколько значений последовательно отправляются. Каждое добавочное значение посылает только около 10 нс (или даже ниже).

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

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

Другая проблема, которую решает логика кэша (помимо чтения вперед и сохранения/освобождения шести бит на адресной шине), заключается в том, как организован кеш. Например, если кеш будет разделен на 8-байтовые (64-битные) блоки (ячейки), необходимо сохранить адрес ячейки памяти, в этой ячейке кеша будет указано значение вместе с ним. Если адрес также будет 64-битным, это означает, что половина размера кеша потребляется по адресу, в результате чего накладные расходы составляют 100%.

Поскольку строка кэша имеет 64 байта, а CPU может использовать 64 бит - 6 бит = 58 бит (нет необходимости хранить нулевые биты слишком правильно), мы можем кэшировать 64 байта или 512 бит с накладными расходами 58 бит (11% накладных расходов). На самом деле хранящиеся адреса еще меньше, но есть информация о статусе (например, строка кэша действительна и точна, грязна и нуждается в написании обратно в ram и т.д.).

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

В частности, особенно важно синхронизировать доступ к кеш-памяти между различными виртуальными ядрами, их независимые множественные процессоры на одно ядро ​​и, наконец, несколько процессоров на одной материнской плате (в которых есть платы, вмещающие до 48 процессоров и более).

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

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

Ответ 4

Процессоры могут иметь многоуровневые кеши (L1, L2, L3), и они отличаются по размеру и скорости.

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

Подробнее о ветвь-предсказатель, кеш процессора и политики замены.

Это непростая задача. Если в конце дня все, что вам нужно, это тест производительности, вы можете использовать инструмент, например Cachegrind. Однако, поскольку это симуляция, ее результат может в некоторой степени отличаться.

Ответ 5

Я не могу сказать точно, что все аппаратные средства отличаются друг от друга, но, как правило, "64 байта начинаются с ближайшей границы в 64 байта ниже", поскольку это очень быстрая и простая операция для CPU.