Локальность памяти и производительность

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

Иерархия кэша ЦП

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

  • Кэш L1 (уровень 1) : самый маленький и самый быстрый (~1 нс). На процессоре с частотой 3 ГГц это примерно 3 тактовых цикла.
  • Кэш L2 (уровень 2) : больше по размеру и немного медленнее (~3-5 нс, или ~10-15 циклов).
  • Кэш L3 (уровень 3) : Самый большой кэш (~10-20 нс, или ~30-60 циклов).
  • Основная память (DRAM) : самая большая и самая медленная (~100 нс+ или ~300+ циклов).

Пирамида задержки памяти

Контекстуализация задержки: стоимость задержки

Чтобы понять влияние этих цифр, рассмотрим современный суперскалярный процессор, способный выполнять от 4 до 8 инструкций за такт.

Если процессор не затрагивает все кэш-памяти и вынужден ждать 100 нс (300 циклов) для чтения данных из DRAM:

  • Потеряно циклов : ~300 циклов.
  • «Потерянные» инструкции : от 1200 до 2400 инструкций , которые могли бы быть выполнены, если бы данные уже находились в локальном регистре или кэше L1.

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

Количество инструкций за цикл (IPC)

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

  • Высокий показатель IPC (например, 3,0–5,0) : процессор работает с высокой эффективностью, вероятно, обрабатывая большую часть данных в кэшах L1/L2 или регистрах.
  • Низкий показатель IPC (например, < 0,5) : процессор сильно ограничен производительностью. Даже если в системных мониторах отображается 100% загрузка процессора, на самом деле он большую часть времени тратит на ожидание доступа к памяти — состояние, известное как задержка доступа к памяти .

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

Строки кэша

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

Механика кэш-линии

TLB (буфер резервного перевода)

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


Характеристики оборудования: Pixel 10 Pro Fold

Для выполнения следующих упражнений мы использовали аппаратное устройство Pixel 10 Pro Fold . Это устройство оснащено процессором Google Tensor G5 .

Проверка оборудования

Для понимания работы подсистемы памяти мы сначала рассмотрим конфигурацию процессора и параметры кэша.

# Check CPU architecture and core parts
adb shell cat /proc/cpuinfo | grep 'CPU part' | sort -u
# Output:
# CPU part  : 0xd8b
# CPU part  : 0xd8c
# CPU part  : 0xd90

# Check cache line size
adb shell getconf -a | grep CACHE_LINESIZE
# Output:
# LEVEL1_ICACHE_LINESIZE             64
# LEVEL1_DCACHE_LINESIZE             64

Расшифровка компонентов ЦП

Значения CPU part в файле /proc/cpuinfo представляют собой шестнадцатеричные идентификаторы ядер процессоров ARM. Для SoC Laguna, используемого в Pixel 10 Pro Fold, они соответствуют следующим значениям:

  • 0xd8b : ARM Cortex-A520 (энергоэффективные ядра)
  • 0xd90 : ARM Cortex-A720 (высокопроизводительные ядра)
  • 0xd8c : ARM Cortex-X4 (основное ядро)

Такая конфигурация 4+3+1 распространена в современных мобильных SoC, где разные кластеры могут иметь разные размеры кэша и задержки.


Типы местности

Эффективное проектирование программного обеспечения опирается на два основных типа локальности:

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

Практическое упражнение: измерение локальности с помощью simpleperf

В этом упражнении мы будем использовать simpleperf для мониторинга аппаратных счетчиков производительности при выполнении двух различных обходов матрицы размером 256 МБ.

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

1. Запустите с помощью Simpleperf

Загрузите исполняемый файл, убедитесь, что он исполняемый, и используйте simpleperf stat для измерения событий кэша и TLB. Мы используем суффикс :u для измерения событий в пользовательском пространстве. Для доступа к аппаратным счетчикам PMU на большинстве устройств для выполнения этих команд требуется adb root

adb root
adb shell "chmod +x /data/local/tmp/LocalityLab"

Профиль, строка-основа:

adb shell "simpleperf stat -e cpu-cycles:u,instructions:u,cache-misses:u,L1-dcache-load-misses:u,dTLB-load-misses:u /data/local/tmp/LocalityLab row"

Основной раздел профиля:

adb shell "simpleperf stat -e cpu-cycles:u,instructions:u,cache-misses:u,L1-dcache-load-misses:u,dTLB-load-misses:u /data/local/tmp/LocalityLab col"

2. Пример измерений (Pixel 10 Pro Fold)

Следующие результаты были получены на аппаратном устройстве Pixel 10 Pro Fold:

Метрика Главный ряд (дружественный) Главный столбец (недружелюбный) Разница
Время выполнения 0,83 секунды 68,3 секунды примерно в 82 раза медленнее
Инструкции 5,27 миллиарда 10,20 миллиарда примерно в 1,9 раза больше
Циклы ЦП 1,20 миллиарда 62,18 миллиарда ~52 раза больше
Количество инструкций за цикл (IPC) 4.40 0,16 Эффективность снижена в 27 раз.
Промахи кэша данных L1 210 миллионов 3,369 миллионов В 16 раз больше промахов
Промахи при загрузке dTLB 0,13 миллиона 2,888 миллионов В 22 000 раз больше промахов

3. Анализ результатов

  • Сбой IPC : В построчном тесте процессор достигает показателя IPC 4,40, что указывает на эффективное выполнение нескольких инструкций за цикл. В столбцовом тесте показатель IPC падает до 0,16. Это означает, что процессор простаивает 96% времени , ожидая поступления данных из DRAM.
  • Узкое место TLB : Наиболее существенное различие заключается в промахах при загрузке dTLB . Последовательный доступ (построчный) остается в пределах одних и тех же страниц памяти, что приводит к очень небольшому количеству промахов TLB. Переходы между столбцами (столбцовый) заставляют процессор постоянно обращаться к новым страницам, перегружая TLB и вынуждая выполнять дорогостоящие обходы таблицы страниц.
  • Эффективность кэша : Поколоночный обход кэша приводит к увеличению количества промахов в кэше L1 в 16 раз, заставляя процессор постоянно получать данные из гораздо более медленного кэша L3 или DRAM.

Наблюдение: Хотя оба обхода выполняли одну и ту же логическую операцию над одними и теми же данными, обход по столбцам оказался более чем в 80 раз медленнее . Эта огромная разница полностью обусловлена ​​тем, как шаблон доступа взаимодействует с физической реальностью подсистемы памяти процессора.


← Темы | ↑ Вверх | Привязки сервисов →