Влияет кэш. Галерея эффектов кэшей процессоров. Методы записи кэша

Влияет кэш. Галерея эффектов кэшей процессоров. Методы записи кэша

03.04.2019

Кэш центрального процессора

Функционирование

Диаграмма кэша памяти ЦПУ

Кэш - это память с большей скоростью доступа, предназначенная для ускорения обращения к данным, содержащимся постоянно в памяти с меньшей скоростью доступа (далее «основная память»). Кэширование применяется ЦПУ , жёсткими дисками , браузерами и веб-серверами .

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

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

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

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

При модификации элементов данных в кэше выполняется их обновление в основной памяти. Задержка во времени между модификацией данных в кэше и обновлением основной памяти управляется так называемой политикой записи .

В кэше с немедленной записью каждое изменение вызывает синхронное обновление данных в основной памяти.

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

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

Кэш центрального процессора

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

Уровни кэша

Кэш центрального процессора разделён на несколько уровней. Для универсальных процессоров - до 3. Кэш-память уровня N+1 как правило больше по размеру и медленнее по скорости обращения и передаче данных, чем кэш-память уровня N.

Самой быстрой памятью является кэш первого уровня - L1-cache. По сути, она является неотъемлемой частью процессора, поскольку расположена на одном с ним кристалле и входит в состав функциональных блоков. Состоит из кэша команд и кэша данных. Некоторые процессоры без L1 кэша не могут функционировать. На других его можно отключить, но тогда значительно падает производительность процессора. L1 кэш работает на частоте процессора, и, в общем случае, обращение к нему может производиться каждый такт (зачастую является возможным выполнять даже несколько чтений/записей одновременно). Латентность доступа обычно равна 2−4 тактам ядра. Объём обычно невелик - не более 128 Кбайт.

Вторым по быстродействию является L2-cache - кэш второго уровня. Обычно он расположен либо на кристалле, как и L1, либо в непосредственной близости от ядра, например, в процессорном картридже (только в слотовых процессорах). В старых процессорах - набор микросхем на системной плате. Объём L2 кэша от 128 Кбайт до 1−12 Мбайт. В современных многоядерных процессорах кэш второго уровня, находясь на том же кристалле, является памятью раздельного пользования - при общем объёме кэша в 8 Мбайт на каждое ядро приходится по 2 Мбайта. Обычно латентность L2 кэша, расположенного на кристалле ядра, составляет от 8 до 20 тактов ядра. В отличие от L1 кэша, его отключение может не повлиять на производительность системы. Однако, в задачах, связанных с многочисленными обращениями к ограниченной области памяти, например, СУБД , производительность может упасть в десятки раз.

Кэш третьего уровня наименее быстродействующий и обычно расположен отдельно от ядра ЦП, но он может быть очень внушительного размера - более 32 Мбайт. L3 кэш медленнее предыдущих кэшей, но всё равно значительно быстрее, чем оперативная память. В многопроцессорных системах находится в общем пользовании.

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

Ассоциативность кэша

Одна из фундаментальных характеристик кэш-памяти - уровень ассоциативности - отображает её логическую сегментацию. Дело в том, что последовательный перебор всех строк кэша в поисках необходимых данных потребовал бы десятков тактов и свёл бы на нет весь выигрыш от использования встроенной в ЦП памяти. Поэтому ячейки ОЗУ жёстко привязываются к строкам кэш-памяти (в каждой строке могут быть данные из фиксированного набора адресов), что значительно сокращает время поиска. С каждой ячейкой ОЗУ может быть связано более одной строки кэш-памяти: например, n -канальная ассоциативность (англ. n -way set associative ) обозначает, что информация по некоторому адресу оперативной памяти может храниться в n местах кэш-памяти.

При одинаковом объеме кэша схема с большей ассоциативностью будет наименее быстрой, но наиболее эффективной.

Кэширование внешних накопителей

Многие периферийные устройства хранения данных используют кэш для ускорения работы, в частности, жёсткие диски используют кэш-память от 1 до 32 Мбайт (модели с поддержкой одновременно и имеет смысл прочитать блок один раз, затем хранить одну копию блока в оперативной памяти для всех процессов;

  • доступ к некоторым блокам оперативной памяти происходит гораздо чаще, чем к другим, поэтому использование кэширования для таких блоков в целом увеличивает производительность системы;
  • для некоторых блоков памяти внешних накопителей не требуется непосредственной записи после модификации, и использование кэша для таких блоков оптимизирует использование ввода-вывода.
  • Кэширование, выполняемое операционной системой

    Кэш оперативной памяти состоит из следующих элементов:

    1. набор страниц оперативной памяти, разделённых на буферы, равные по длине блоку данных соответствующего устройства внешней памяти;
    2. набор заголовков буферов, описывающих состояние соответствующего буфера;
    3. хеш-таблицы , содержащей соответствие номера блока заголовку;
    4. списки свободных буферов.

    Алгоритм работы кэша с отложенной записью

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

    1. пытается найти в хеш-таблице заголовок буфера с заданным номером;
    2. в случае, если полученный буфер занят, ждёт его освобождения;
    3. в случае, если буфер не найден в хеш-таблице, берёт первый буфер из хвоста списка свободных;
    4. в случае, если список свободных буферов пуст, то выполняется алгоритм вытеснения (см. ниже);
    5. в случае, если полученный буфер помечен как «грязный», выполняет асинхронную запись содержимого буфера во внешнюю память.
    6. удаляет буфер из хеш-таблицы, если он был помещён в неё;
    7. помещает буфер в хеш-таблицу с новым номером.

    Процесс читает данные в полученный буфер и освобождает его. В случае модификации процесс перед освобождением помечает буфер как «грязный». При освобождении буфер помещается в голову списка свободных буферов.

    Таким образом:

    1. если процесс прочитал некоторый блок в буфер, то велика вероятность, что другой процесс при чтении этого блока найдёт буфер в оперативной памяти;
    2. запись данных во внешнюю память выполняется только тогда, когда не хватает «чистых» буферов, либо по запросу.

    Алгоритм вытеснения

    Если список свободных буферов пуст, то выполняется алгоритм вытеснения буфера. Алгоритм вытеснения существенно влияет на производительность кэша. Существуют следующие алгоритмы:

    1. LRU (Least Recently Used) - вытесняется буфер, неиспользованный дольше всех;
    2. MRU (Most Recently Used) - вытесняется последний использованный буфер;
    3. LFU (Least Frequently Used) - вытесняется буфер, использованный реже всех;
    4. ARC (англ.) (Adaptive Replacement Cache) - алгоритм вытеснения, комбинирующий LRU и LFU, запатентованный

      Программное кэширование

      Политика записи при кэшировании

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

      Существуют две основные политики записи кэш-памяти - сквозная запись (write-through) и отложенная запись (write-back).

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

      Кэширование интернет-страниц

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

    Кэш — память (кеш , cash , буфер — eng.) — применяется в цифровых устройствах, как высокоскоростной буфер обмена. Кэш память можно встретить на таких устройствах компьютера как , процессоры, сетевые карты, приводы компакт дисков и многих других.

    Принцип работы и архитектура кэша могут сильно отличаться.

    К примеру, кэш может служить как обычный буфер обмена . Устройство обрабатывает данные и передаёт их в высокоскоростной буфер, где контроллёр передаёт данные на интерфейс. Предназначен такой кэш для предотвращения ошибок, аппаратной проверки данных на целостность, либо для кодировки сигнала от устройства в понятный сигнал для интерфейса, без задержек. Такая система применяется например в CD/DVD приводах компакт дисков.

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

    Такая архитектура чаще всего встречается на жёстких дисках, и центральных процессорах (CPU ).

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

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

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

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

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

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

    Другие функции и особенности .

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

    В CPU и GPU существует иерархия кэш памяти , которая позволяет разделять данные для отдельных ядер и общие. Хотя почти все данные из кэша второго уровня, всё равно копируются в третий, общий уровень, но не всегда. Первый уровень кеша — самый быстрый, а каждый последующий всё медленней, но больше по размеру.

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

    Размер кэша, влияние на производительность и другие характеристики .

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

    Большой кеш — это большой бюджет . В серверных процессорах (CPU ), кэш может использовать до 80% транзисторного бюджета. Во первых, это сказывается на конечной стоимости, а во вторых увеличивается энергопотребление и тепловыделение, которое не сопоставимо с увеличенной на несколько процентов производительностью.

    • Перевод

    Почти все разработчики знают, что кэш процессора - это такая маленькая, но быстрая память, в которой хранятся данные из недавно посещённых областей памяти - определение краткое и довольно точное. Тем не менее, знание «скучных» подробностей относительно механизмов работы кэша необходимо для понимания факторов влияющих на производительность кода.

    В этой статье мы рассмотрим ряд примеров иллюстрирующих различные особенности работы кэшей и их влияние на производительность. Примеры будут на C#, выбор языка и платформы не так сильно влияет на оценку производительности и конечные выводы. Естественно, в разумных пределах, если вы выберите язык, в котором чтение значения из массива равносильно обращению к хеш-таблице, никаких результатов пригодных к интерпретации вы не получите. Курсивом идут примечания переводчика.

    Habracut - - -

    Пример 1: доступ к памяти и производительность

    Как вы думаете, насколько второй цикл быстрее первого?
    int arr = new int ;

    // первый
    for (int i = 0; i < arr.Length; i++) arr[i] *= 3;

    // второй
    for (int i = 0; i < arr.Length; i += 16) arr[i] *= 3;


    Первый цикл умножает все значения массива на 3, второй цикл только каждое шестнадцатое значение. Второй цикл совершает только 6% работы первого цикла, но на современных машинах оба цикла выполняются примерно за равное время: 80 мс и 78 мс соответственно (на моей машине).

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

    Пример 2: влияние строк кэша

    Копнём глубже - попробуем другие значения шага, не только 1 и 16:
    for (int i = 0; i < arr.Length; i += K /* шаг */ ) arr[i] *= 3;

    Вот время работы этого цикла для различных значений шага K:

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

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

    Из-за того, что 16 значений типа int занимают 64 байта, циклы с шагами от 1 до 16 обращаются к одинаковому количеству строк кэша, точнее говоря, ко всем строкам кэша массива. При шаге 32, обращение происходит к каждой второй строке, при шаге 64, к каждой четвёртой.

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

    Пример 3: размеры кэшей первого и второго уровня (L1 и L2)

    Современные процессоры, как правило, имеют два или три уровня кэшей, обычно их называют L1, L2 и L3. Для того, чтобы узнать размеры кэшей различных уровней, можно воспользоваться утилитой CoreInfo или функцией Windows API GetLogicalProcessorInfo . Оба способа так же предоставляют информацию о размере строки кэша для каждого уровня.

    На моей машине CoreInfo сообщает о кэшах данных L1 объёмом по 32 Кбайт, кэшах инструкций L1 объёмом по 32 Кбайт и кэшах данных L2 объёмом по 4 Мбайт. Каждое ядро имеет свои персональные кэши L1, кэши L2 общие для каждой пары ядер:

    Logical Processor to Cache Map: *--- Data Cache 0, Level 1, 32 KB, Assoc 8, LineSize 64 *--- Instruction Cache 0, Level 1, 32 KB, Assoc 8, LineSize 64 -*-- Data Cache 1, Level 1, 32 KB, Assoc 8, LineSize 64 -*-- Instruction Cache 1, Level 1, 32 KB, Assoc 8, LineSize 64 **-- Unified Cache 0, Level 2, 4 MB, Assoc 16, LineSize 64 --*- Data Cache 2, Level 1, 32 KB, Assoc 8, LineSize 64 --*- Instruction Cache 2, Level 1, 32 KB, Assoc 8, LineSize 64 ---* Data Cache 3, Level 1, 32 KB, Assoc 8, LineSize 64 ---* Instruction Cache 3, Level 1, 32 KB, Assoc 8, LineSize 64 --** Unified Cache 1, Level 2, 4 MB, Assoc 16, LineSize 64
    Проверим эту информацию экспериментально. Для этого, пройдёмся по нашему массиву инкрементируя каждое 16-ое значение - простой способ изменить данные в каждой строке кэша. При достижении конца, возвращаемся к началу. Проверим различные размеры массива, мы должны увидеть падение производительности когда массив перестаёт помещаться в кэши разных уровней.

    Код такой:

    int steps = 64 * 1024 * 1024; // количество итераций
    int lengthMod = arr.Length - 1; // размер массива -- степень двойки

    for (int i = 0; i < steps; i++)
    {
    // x & lengthMod = x % arr.Length, ибо степени двойки
    arr[(i * 16) & lengthMod]++;
    }


    Результаты тестов:

    На моей машине заметны падения производительности после 32 Кбайт и 4 Мбайт - это и есть размеры кэшей L1 и L2.

    Пример 4: параллелизм инструкций

    Теперь давайте взглянем на кое-что другое. По вашему мнению, какой из этих двух циклов выполнится быстрее?
    int steps = 256 * 1024 * 1024;
    int a = new int ;

    // первый
    for (int i = 0; i < steps; i++) { a++; a++; }

    // второй
    for (int i = 0; i < steps; i++) { a++; a++; }


    Оказывается, второй цикл выполняется почти в два раза быстрее, по крайней мере, на всех протестированных мной машинах. Почему? Потому, что команды внутри циклов имеют разные зависимости по данным. Команды первого имеют следующую цепочку зависимостей:

    Во втором цикле зависимости такие:

    Функциональные части современных процессоров способны выполнять определённое число некоторых операций одновременно, как правило, не очень большое число. Например, возможен параллельный доступ к данным из кэша L1 по двум адресам, так же возможно одновременное выполнение двух простых арифметических команд. В первом цикле процессор не может задействовать эти возможности, но может во втором.

    Пример 5: ассоциативность кэша

    Один из ключевых вопросов, на который необходимо дать ответ при проектировании кэша - могут ли данные из определённой области памяти храниться в любых ячейках кэша или только в некоторых из них. Три возможных решения:
    1. Кэш прямого отображения , данные каждой строки кэша в оперативной памяти хранятся только в одной заранее определённой ячейке кэша. Простейший способ вычисления отображения: индекс_строки_в_памяти % количество_ячеек_кэша. Две строки, отображённые на одну и ту же ячейку, не могут находится в кэше одновременно.
    2. N-входовый частично-ассоциативный кэш , каждая строка может храниться в N различных ячейках кэша. Например, в 16-входовом кэше строка может храниться в одной из 16-ти ячеек составляющих группу. Обычно, строки с равными младшими битами индексов разделяют одну группу.
    3. Полностью ассоциативный кэш , любая строка может быть сохранена в любую ячейку кэша. Решение эквивалентно хеш-таблице по своему поведению.
    Кэши прямого отображения подвержены конфликтам, например, когда две строки соревнуются за одну ячейку, поочерёдно вытесняя друг-друга из кэша, эффективность очень низка. С другой стороны, полностью ассоциативные кэши, хотя и лишены этого недостатка, очень сложны и дороги в реализации. Частично-ассоциативные кэши - типичный компромисс между сложностью реализации и эффективностью.

    К примеру, на моей машине кэш L2 размером в 4 Мбайт является 16-входовым частично-ассоциативным кэшем. Вся оперативная память разделена на множества строк по младшим битам их индексов, строки из каждого множества соревнуются за одну группу из 16 ячеек кэша L2.

    Так как кэш L2 имеет 65 536 ячеек (4 * 2 20 / 64) и каждая группа состоит из 16 ячеек, всего мы имеем 4 096 групп. Таким образом, младшие 12 битов индекса строки определяют к какой группе относится эта строка (2 12 = 4 096). В результате, строки с адресами кратными 262 144 (4 096 * 64) разделяют одну и ту же группу из 16-ти ячеек и соревнуются за место в ней.

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

    public static long UpdateEveryKthByte(byte arr, int K)
    {
    const int rep = 1024 * 1024; // количество итераций

    Stopwatch sw = Stopwatch.StartNew();

    int p = 0;
    for (int i = 0; i < rep; i++)
    {
    arr[p]++;

    P += K; if (p >= arr.Length) p = 0;
    }

    Sw.Stop();
    return sw.ElapsedMilliseconds;
    }


    Метод инкрементирует каждый K-ый элемент массива. По достижении конца, начинаем заново. После довольно большого количества итераций (2 20), останавливаемся. Я сделал прогоны для различных размеров массива и значений шага K. Результаты (синий - большое время работы, белый - маленькое):

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

    Разберёмся с синими областями:

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

      Некоторые из плохих значений шага - степени двойки: 256 и 512. Для примера рассмотрим шаг 512 и массив в 8 Мбайт. При этом шаге, в массиве имеются 32 участка (8 * 2 20 / 262 144), которые ведут борьбу друг с другом за ячейки в 512-ти группах кэша (262 144 / 512). Участка 32, а ячеек в кэше под каждую группу только 16, поэтому места на всех не хватает.

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

    2. Почему вертикальные линии обрываются на границе в 4 Мбайт? При размере массива в 4 Мбайт или меньше, 16-входовый кэш ведёт себя так же как и полностью ассоциативный, то есть может вместить все данные массива без конфликтов. Имеется не более 16-ти областей ведущих борьбу за одну группу кэша (262 144 * 16 = 4 * 2 20 = 4 Мбайт).
    3. Почему слева вверху находится большой синий треугольник? Потому, что при маленьком шаге и большом массиве кэш не в состоянии уместить все необходимые данные. Степень ассоциативности кэша играет тут второстепенную роль, ограничение связано с размером кэша L2.

      Например, при размере массива в 16 Мбайт и шаге 128, мы обращаемся к каждому 128-му байту, таким образом, модифицируя каждую вторую строку кэша массива. Чтобы сохранить каждую вторую строку в кэше, необходим его объём в 8 Мбайт, но на моей машине есть только 4 Мбайт.

      Даже если бы кэш был полностью ассоциативным, это не позволило бы сохранить в нём 8 Мбайт данных. Заметьте, что в уже рассмотренном примере с шагом 512 и размером массива 8 Мбайт, нам необходим только 1 Мбайт кэша, чтобы сохранить все нужные данные, но это невозможно сделать из-за недостаточной ассоциативности кэша.

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

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

    Обнаруженные эффекты сохраняются и при больших значениях параметров:

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

    Пример 6: ложное разделение кэша

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

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

    Продемонстрируем эту проблему на следующем коде:

    private static int s_counter = new int ;

    private void UpdateCounter(int position)
    {
    for (int j = 0; j < 100000000; j++)
    {
    s_counter = s_counter + 3;
    }
    }


    Если на своей четырёхядерной машине я вызову этот метод с параметрами 0, 1, 2, 3 одновременно из четырёх потоков, то время работы составит 4.3 секунды . Но если я вызову метод с параметрами 16, 32, 48, 64, то время работы составит только 0.28 секунды .

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

    Пример 7: сложность железа

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

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

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

    Вот другой пример странных причуд железа:

    private static int A, B, C, D, E, F, G;

    private static void Weirdness()
    {
    for (int i = 0; i < 200000000; i++)
    {
    <какой-то код>
    }
    }


    Если вместо <какой-то код> подставить три разных варианта, можно получить следующие результаты:

    Инкрементирование полей A, B, C, D занимает больше времени, чем инкрементирование полей A, C, E, G. Что ещё страннее, инкрементирование полей A и C занимает больше времени, чем полей A, C и E, G. Не знаю точно каковы причины этого, но возможно они связаны с банками памяти (да-да, с обычными трёхлитровыми сберегательными банками памяти, а не то, что вы подумали ). Имеющих соображения на этот счёт, прошу высказываться в комментариях.

    У меня на машине вышеописанного не наблюдается, тем не менее, иногда бывают аномально плохие результаты - скорее всего, планировщик задач вносит свои «коррективы».

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

    Заключение

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

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

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

    Уровни кэша

    Кэш центрального процессора разделён на несколько уровней. В универсальном процессоре в настоящее время число уровней может достигать 3. Кэш-память уровня N+1 как правило больше по размеру и медленнее по скорости доступа и передаче данных, чем кэш-память уровня N.

    Самой быстрой памятью является кэш первого уровня -- L1-cache. По сути, она является неотъемлемой частью процессора, поскольку расположена на одном с ним кристалле и входит в состав функциональных блоков. В современных процессорах обычно кэш L1 разделен на два кэша, кэш команд (инструкций) и кэш данных (Гарвардская архитектура). Большинство процессоров без L1 кэша не могут функционировать. L1 кэш работает на частоте процессора, и, в общем случае, обращение к нему может производиться каждый такт. Зачастую является возможным выполнять несколько операций чтения/записи одновременно. Латентность доступа обычно равна 2?4 тактам ядра. Объём обычно невелик -- не более 384 Кбайт.

    Вторым по быстродействию является L2-cache -- кэш второго уровня, обычно он расположен на кристалле, как и L1. В старых процессорах -- набор микросхем на системной плате. Объём L2 кэша от 128 Кбайт до 1?12 Мбайт. В современных многоядерных процессорах кэш второго уровня, находясь на том же кристалле, является памятью раздельного пользования -- при общем объёме кэша в nM Мбайт на каждое ядро приходится по nM/nC Мбайта, где nC количество ядер процессора. Обычно латентность L2 кэша, расположенного на кристалле ядра, составляет от 8 до 20 тактов ядра.

    Кэш третьего уровня наименее быстродействующий, но он может быть очень внушительного размера -- более 24 Мбайт. L3 кэш медленнее предыдущих кэшей, но всё равно значительно быстрее, чем оперативная память. В многопроцессорных системах находится в общем пользовании и предназначен для синхронизации данных различных L2.

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

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

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

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

    Не смотря на такую разницу в скорости обработки информации, процессор ПК не простаивает без дела и не ожидает, когда ОЗУ выдаст и примет данные. Процессор всегда работает и всё благодаря присутствию в нем кэш памяти.

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

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

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

    Основные типы и уровни кэш-памяти L1 L2 L3

    Кэш память выполнена в виде микросхем статической оперативной памяти (SRAM), которые устанавливаются на системной плате либо встроены в процессор. В сравнении с другими видами памяти, статическая память способна работать на очень больших скоростях.

    Скорость кэша зависит от объема конкретной микросхемы, Чем больше объем микросхемы, тем труднее добиться высокой скорости для её работы. Учитывая данную особенность, при изготовлении кэш память процессора выполняют в виде нескольких небольших блоков, именуемых уровнями. Самой распространенной на сегодняшний день считается трехуровневая система кеша L1,L2, L3:

    Кэш память первого уровня L1 — самая маленькая по объему (всего несколько десятков килобайт), но самая быстрая по скорости и наиболее важная. Она содержит данные наиболее часто используемые процессором и работает без задержек. Обычно количество микросхем памяти уровня L1 равно количеству ядер процессора, при этом каждое ядро получает доступ только к своей микросхеме L1.

    Кэш память уровня L2 по скорости уступает памяти L1, но выигрывает в объеме, который измеряется уже в нескольких сотнях килобайт. Она предназначена для временного хранения важной информации, вероятность обращения к которой ниже, чем у информации хранящейся в кэше L1.

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

    Некоторые модели процессоров выполнены с двумя уровнями кэш памяти, в которых L2 совмещает все функции L2 и L3.

    Когда полезен большой объем кэша.

    Значительный эффект от большого объема кэша вы ощутите при использовании программ архиваторов, в 3D играх, во время обработки и кодирования видео. В относительно «легких» программах и приложениях разница практически не заметна (офисные программы, плееры и т.п).



    © 2024 beasthackerz.ru - Браузеры. Аудио. Жесткий диск. Программы. Локальная сеть. Windows