Прежде всего, доступ к основной памяти очень дорогой. В настоящее время процессор с частотой 2 ГГц (самый медленный один раз) имеет такты 2 Гц (циклов) в секунду. Процессор (виртуальное ядро в настоящее время) может извлекать значение из своих регистров один раз за такт. Поскольку виртуальное ядро состоит из нескольких блоков обработки (ALU - арифметико-логический блок, FPU и т. Д.), Оно может фактически обрабатывать определенные инструкции параллельно, если это возможно.
Доступ к основной памяти стоит около 70 нс до 100 нс (DDR4 немного быстрее). На этот раз в основном ищем кеш L1, L2 и L3 и затем попадаем в память (посылаем команду контроллеру памяти, которая отправляет его в банки памяти), ждем ответа и все готово.
100 нс означает около 200 тиков. Таким образом, в основном, если программа всегда будет пропускать кеши, к которым обращается каждая память, центральный процессор будет тратить около 99,5% своего времени (если он только читает память) в ожидании памяти.
Для ускорения работы предусмотрены кэши L1, L2, L3. Они используют память, непосредственно размещенную на чипе, и используют различные типы транзисторных схем для хранения данных битов. Это занимает больше места, больше энергии и является более дорогостоящим, чем основная память, поскольку ЦП обычно создается с использованием более продвинутой технологии, и производственный сбой в памяти L1, L2, L3 имеет возможность сделать ЦП бесполезным (дефект), поэтому большие кэши L1, L2, L3 увеличивают частоту ошибок, что снижает выход, что напрямую снижает ROI. Таким образом, существует огромный компромисс, когда дело доходит до доступного размера кэша.
(в настоящее время создается больше кэшей L1, L2, L3, чтобы иметь возможность деактивировать определенные части, чтобы уменьшить вероятность того, что фактическим производственным дефектом является область кэш-памяти, отображающая дефект ЦП в целом).
Чтобы дать представление о времени (источник: затраты на доступ к кешам и памяти )
- Кэш-память первого уровня: от 1 до 2 нс (2-4 цикла)
- Кэш-память второго уровня: от 3 до 5 нс (6-10 циклов)
- Кэш-память третьего уровня: от 12 до 20 нс (24-40 циклов)
- Оперативная память: 60 нс (120 циклов)
Поскольку мы смешиваем разные типы процессоров, это всего лишь приблизительные оценки, но они дают хорошее представление о том, что происходит на самом деле, когда выбирается значение памяти, и мы можем столкнуться с ошибкой или пропуском в определенном слое кэша.
Таким образом, кеш существенно ускоряет доступ к памяти (60 нс против 1 нс).
Извлечение значения, сохранение его в кеше для возможности перечитывания его полезно для переменных, к которым часто обращаются, но для операций копирования в память это все равно будет медленным, так как кто-то просто читает значение, записывает значение куда-то и никогда не читает значение опять же ... нет попаданий в кэш, очень медленно (кроме этого может происходить параллельно, так как у нас неправильное выполнение).
Эта копия памяти настолько важна, что есть разные способы ускорить ее. В первые дни память часто могла копировать память вне процессора. Он был обработан контроллером памяти напрямую, поэтому операция копирования памяти не загрязняла кэши.
Но помимо простой копии памяти другой последовательный доступ к памяти был довольно распространенным. Примером является анализ ряда информации. Наличие массива целых чисел и вычисление суммы, среднего, среднего или даже более простого нахождения определенного значения (фильтр / поиск) были еще одним очень важным классом алгоритмов, запускаемых каждый раз на любом центральном процессоре общего назначения.
Таким образом, при анализе схемы доступа к памяти стало очевидно, что данные читаются последовательно очень часто. Была высокая вероятность того, что если программа читает значение по индексу i, программа также будет читать значение i + 1. Эта вероятность немного выше, чем вероятность того, что та же программа также прочитает значение i + 2 и так далее.
Таким образом, учитывая адрес памяти, было (и остается) хорошей идеей читать вперед и извлекать дополнительные значения. Это причина, почему есть режим повышения.
Доступ к памяти в режиме повышения означает, что адрес отправляется, а несколько значений отправляются последовательно. Каждая дополнительная отправка значения занимает всего около 10 нс (или даже ниже).
Еще одной проблемой был адрес. Отправка адреса занимает время. Чтобы адресовать большую часть памяти, необходимо отправить большие адреса. В первые дни это означало, что адресная шина была недостаточно большой для отправки адреса за один цикл (тик), и для отправки адреса требовалось более одного цикла, добавляя больше задержки.
Например, строка кэша размером 64 байта означает, что память разделена на отдельные (не перекрывающиеся) блоки памяти размером 64 байта. 64 байта означают, что начальный адрес каждого блока имеет шесть младших битов адреса, чтобы всегда быть нулями. Таким образом, отправка этих шести нулевых битов каждый раз не требуется, увеличивая адресное пространство в 64 раза для любого количества адресов ширины шины (эффект приветствия).
Другая проблема, которую решает строка кэша (помимо чтения вперед и сохранения / освобождения шести битов на адресной шине), заключается в том, как организован кэш. Например, если кэш будет разделен на 8-байтовые (64-битные) блоки (ячейки), необходимо хранить адрес ячейки памяти, для которой эта ячейка кэша содержит значение вместе с ней. Если адрес также будет 64-битным, это означает, что половина размера кэша используется адресом, что приводит к накладным расходам в 100%.
Поскольку длина строки кэша составляет 64 байта, а процессор может использовать 64 бита - 6 бит = 58 бит (нет необходимости хранить нулевые биты слишком правильно), это означает, что мы можем кэшировать 64 байта или 512 бит с объемом служебной информации 58 бит (11% служебной нагрузки). В действительности хранимые адреса даже меньше, чем это, но есть информация о состоянии (например, является ли строка кэша действительной и точной, грязной и требует обратной записи в оперативной памяти и т. Д.).
Другим аспектом является то, что у нас есть набор-ассоциативный кэш. Не каждая ячейка кеша может хранить определенный адрес, но только его подмножество. Это делает необходимые биты сохраненных адресов еще меньше, обеспечивает параллельный доступ к кешу (к каждому подмножеству можно обращаться один раз, но независимо от других подмножеств).
Особенно важно синхронизировать доступ к кэш-памяти / памяти между различными виртуальными ядрами, их независимыми несколькими процессорами на ядро и, наконец, несколькими процессорами на одной материнской плате (в которой есть до 48 процессорных плат и более).
Это в основном текущая идея, почему у нас есть строки кэша. Преимущество от чтения вперед очень велико, и наихудший случай чтения одного байта из строки кэша и повторного чтения остальных очень мал, поскольку вероятность очень мала.
Размер строки кэша (64) является разумно выбранным компромиссом между более крупными строками кэша, поэтому маловероятно, что последний байт будет прочитан в ближайшем будущем, а также длительность извлечения полной строки кэша. из памяти (и записать ее обратно), а также накладные расходы на организацию кэша и распараллеливание доступа к кешу и памяти.