На ARM-серверах вопрос о том, кто съедает кэш и полосу памяти, звучит не тише, чем на x86, а инструментов долгое время было меньше. Neoverse-платформы от Ampere, AWS, HiSilicon и NVIDIA принесли в датацентры десятки и сотни ядер, общих кэшей и многослойных мешей, и следить за памятью на них приходится теми же методами, что и везде: разметкой ресурсов и мониторингом. Масштаб узлов давно серверный, у флагманских моделей счёт ядер идёт от 64 до 80 и выше, память объёмом в терабайты сидит за несколькими контроллерами, и один прожорливый поток способен просадить соседей по всему мешу. Архитектурный ответ ARM называется MPAM, Memory Partitioning And Monitoring, это расширение, которое размечает трафик по идентификаторам партиций и подсчитывает расход кэша и полосы на системных компонентах. Пока драйвер MPAM идёт к мейнстриму, рабочую жизнь на ARM-узлах поддерживает набор некоревых PMU и Statistical Profiling Extension, и связка из двух подходов закрывает практические задачи уже сегодня.
Разберём устройство MPAM, ресурсы и мониторы внутри системных компонентов, состояние поддержки в ядре, практический мониторинг через perf и порядок внедрения на реальном сервере.
MPAM как архитектурный ответ ARM на разметку ресурсов
Идея MPAM близка к Intel RDT, но распространена шире по системе. Всякая деталь, которая обслуживает память, кэш уровней, контроллер памяти, SMMU, может быть MPAM System Component, MSC, со своим блоком регистров в MMIO. Каждый исходящий запрос в системе помечается идентификатором партиции PARTID и подгруппой мониторинга PMG, и этот тег путешествует через кэши и меш до контроллера памяти, давая каждому компоненту возможность применить свою политику и посчитать расход. В ARMv8.4-A расширение вошло как FEAT_MPAM, а в регистрах уровня исключения это выглядит как MPAM1_EL1 с полями PARTID_D для данных, PARTID_I для инструкций и PMG, плюс парные регистры для более высоких исключений.
PARTID может занимать до 16 бит, PMG до 8, и именно эта пара определяет, в какую ячейку лягут счётчики. Смысл разметки прямой: гипервизор назначает PARTID виртуальной машине, планировщик задач может менять PARTID по процессам, и трафик каждого участника становится видимым и ограничиваемым на всех уровнях системы сразу. Целевой пользовательский интерфейс у ARM зарезервирован общий с x86: тот же resctrl, чтобы инструменты не пришлось плодить по архитектурам. Отдельный пласт разметки связан с вводом-выводом: SMMU умеет присваивать теги партиций потокам устройств, поэтому трафик сетевого контроллера или ускорителя, идущий через транслятор, размечается наравне с трафиком ядер, и система видит не только процессы, но и периферию.
Ресурсы и мониторы внутри системных компонентов
Каждый MSC описывает свои возможности через группы регистров MPAMFGR, и набор ресурсов варьируется по классу компонента. Кэш поддерживает разбиение на порции через битовую карту MPAMCFG_CPBM, выбираемую по номеру партиции в MPAMCFG_PART_SEL: порция определяет, какие строки кэша закрыты для данной партиции. Контроллер памяти умеет ограничивать полосу через MPAMCFG_MBW_MAX и MPAMCFG_MBW_MIN с настраиваемой точностью, а весовые пропорции задаёт MPAMCFG_MBW_WD, когда нужны мягкие пропорции вместо жёстких потолков.
Мониторинг строится на наборе MSMON, где через MSMON_CFG_MON_SEL выбирается монитор и конфигурируется его поведение, а результаты снимаются из регистров захвата. Монитор CSU считает занятость хранилища кэша в байтах, MBWU считает расход полосы памяти с накоплением, CBCR отслеживает трафик кэша, CPA долю обращений к порциям кэша. Есть схемы переполнения через MSMON_OVSR с прерываниями, чтобы счётчики не молча залипали на максимум, и фильтры по типам транзакций, отделяющие чтения от записей. Сырые показания требуют нормировки: разрядность счётчиков и вес шага различаются между MSC, поэтому перед сравнением показания приводят к байтам или долям согласно параметрам конкретного компонента. Пример разбиения кэша выглядит примитивно: маска вида 0xffff одной партиции и 0xff00 другой делит порции, и прошивка обязана гарантировать непересечение. Конкретный MSC реализует подмножество, и прошивка обязана честно рассказать, что есть, что нет, где границы диапазонов и сколько мониторов реально доступно одновременно.
Состояние поддержки в ядре Linux
Драйвер MPAM в мейнстриме долго шёл через череду ревизий, поэтому сегодня реальный инструментарий ARM-сервера опирается на уже слитые драйверы некоревых PMU. Их состав виден в системе мгновенно:
ls /sys/bus/event_source/devices/
perf list | grep -iE 'cmn|dmc|hisi|qcom|smmu'
Смысл названий стоит выучить. arm_cmn обслуживает меш CMN-600 и CMN-700, где живут события кэшных узлов, координаторов и каналов доступа к памяти. arm_dmc620 описывает контроллер памяти DMC-620 и считает трафик по каналам. Для HiSilicon в ядре есть семейство hisi_uncore с PMU на контроллеры памяти DDRC, узлы когерентности HHA и кэши L3C. qcom_l3_pmu даёт мониторинг L3 на Qualcomm-платформах, arm_smmuv3_pmu показывает трафик через транслятор адресов. Прошивки описывают эти блоки через ACPI, и на свежих серверах перечень устройств получается длинным и говорящим.
Гибридная картина временная, но рабочая: разметку и жёсткие ограничения на части платформ берёт на себя прошивка или гипервизор, а мониторинг закрывают PMU, которых на типовом двухпроцессорном узле набирается полтора десятка. В конфигурации ядра эти блоки живут под знакомыми именами CONFIG_ARM_CMN, CONFIG_ARM_DMC620_PMU, CONFIG_HISI_PMU, CONFIG_QCOM_L3_PMU и CONFIG_ARM_SMMU_V3_PMU, и на собранном дистрибутивном ядре достаточно проверить, что соответствующие модули присутствуют и подцепились. Статус самой серии MPAM-патчей удобно отслеживать по спискам рассылки arm64, где она периодически обновляется очередными ревизиями.
Мониторинг полосы и кэша через perf
Мониторы uncore читаются стандартным perf, и типовой замер начинается с изучения списка событий конкретного PMU, потому что имена различаются между вендорами. Дальше замер сводится к обычной схеме: фоновая нагрузка, stat по интересующим событиям, сопоставление с топологией узла. Для Kunpeng характерны PMU с префиксом hisi_sccl на каждый супер-кластер, для CMN-платформ события собираются через узлы меша с их координаторами, и в обоих случаях полезно сначала снять карту устройств, чтобы понять, какие счётчики относятся к какому NUMA-узлу. Помощники стандартные:
numactl -H
perf stat -a -- sleep 5
perf record -e arm_spe_0// -a -o spe.data -- sleep 10
perf report -i spe.data --sort=dso
Особое место занимает Statistical Profiling Extension. SPE сэмплирует память по инструкциям и показывает, кто именно промахивается в кэши и ходит в память:
perf record -e arm_spe_0// -a -- sleep 10
perf report --sort=dso,sample
Готовая запись даёт профиль по объектным файлам и типам обращений, и узкое место видно не абстрактными счётчиками, а конкретными кодовыми путями. Для задач, где нужно понять, почему полоса памяти кончается, SPE часто полезнее любых uncore-счётчиков, потому что отвечает на вопрос кто и куда, а не только сколько. Настройка буферов и периодов сэмплирования делается атрибутами события, и на горячих нагрузках стоит следить за потерями записей, которые perf показывает в конце сеанса, при необходимости разбивая замер на короткие интервалы.
Схема сравнения до и после работает и здесь: базовый замер с perf stat на эталонной нагрузке, изменение конфигурации, повторный замер, и дельта по событиям контроллера памяти или меша показывает, куда сместился трафик. Для длительных наблюдений perf пишет результаты в файл, который потом читается отчётом, а в системах мониторинга PMU опрашивают по расписанию, поскольку счётчики не требуют специального режима доступа и читаются с достаточными правами так же легко, как обычные файлы.
Практические приёмы изоляции на ARM-узлах
Пока жёсткое программное управление партициями в мейнстриме дозревает, изоляцию на ARM-узлах собирают из доступных кусков. Базой служит NUMA-топология: привязка процессов и прерываний к узлам через numactl и irqbalance даёт предсказуемую локальность, и на платформах с выраженной иерархией меша это работает сильнее, чем выглядит на бумаге. Огромные страницы уменьшают число промахов по таблицам страниц, что на системах с высокой латентностью TLB даёт заметный выигрыш. Изоляция потоков по ядрам вместе с ограничением прерываний на выделенные ядра снижает взаимное загрязнение кэшей, а для жёсткой разметки прерываний системную службу балансировки отключают и прописывают маски в /proc/irq вручную. Огромные страницы на arm64 доступны в размерах 2, 32 и 512 мегабайт в зависимости от прошивки, и выбор размера заметно влияет на поведение транслятора адресов.
Гипервизоры добавляют ещё один слой. KVM на ARM умеет пробрасывать идентификаторы потоков через SMMU, и виртуальные машины с собственными пространствами ввода-вывода получают заметно более честную изоляцию, чем при общей таблице трансляции. Политики памяти на уровне прошивки, там, где вендор их выставляет, комбинируются с этими приёмами, и суммарно даже без готового MPAM-драйвера узел настраивается на предсказуемое поведение. Ключевая привычка здесь та же, что и на x86: сначала измерить, потом изолировать, потом измерить снова.
Диагностика и подводные камни
Первая ловушка ARM-мира это зоопарк имён событий. Один и тот же вопрос, сколько полосы ушло в память, на разных вендорах звучит разными событиями, и копирование готовых команд с x86-статей не работает, поэтому первый шаг на новом железе всегда perf list с чтением документации вендора. Вторая ловушка в разнице между счётчиками: событие на CMN и событие на DMC про один и тот же трафик могут отличаться объёмом, потому что одно считает запросы на входе в меш, а другое транзакции у контроллера, и для отчётов нужно выбрать один уровень и не смешивать.
Третья ловушка это SPE: профилировщик сэмплирует не всё, и на очень горячих путях потери записей искажают картину, поэтому замеры делают с контролем переполнений буферов. Четвёртая связана с MPAM: не все серийные чипы реализуют расширение целиком, у части платформ есть только мониторные части без управляющих, и обещания документации вендора стоит проверять по описанию возможностей из прошивочных таблиц. Пятая общая для всех uncore: счётчики на каждый PMU ограничены, одновременный сбор по всем событиям не влезает, и perf прямо сообщает о нехватке счётчиков при перегруженной группе, поэтому замер планируют под конкретный вопрос. И общий для всех uncore момент: счётчики на каждый PMU ограничены, одновременно следить за всем сразу не выйдет, поэтому замер нужно планировать под конкретный вопрос.
Порядок внедрения мониторинга на ARM-сервере
Свести всё в рабочий процесс помогает фиксированная последовательность:
- снять карту PMU и NUMA-топологии через /sys/bus/event_source/devices и numactl, связав счётчики с узлами;
- подобрать по perf list события полосы и кэша, соответствующие уровню, на котором планируется контроль;
- прогнать эталонную нагрузку с perf stat по выбранным событиям и зафиксировать базовые цифры;
- подключить SPE для разбора горячих путей, если счётчики показывают дефицит, но не говорят, кто виноват;
- выстроить изоляцию через привязки по NUMA, огромные страницы и разнос прерываний;
- закрепить периодический замер в мониторинге, чтобы деградация соседства виднелась до жалоб пользователей.
Ничего экзотического в этой последовательности нет, и её сила именно в повторяемости: один и тот же набор команд на каждом узле фермы даёт сравнимые цифры, а сравнимость важнее абсолютных значений, потому что деградация почти всегда видна в динамике.
В итоге ARM-сервер сегодня живёт в переходном состоянии: архитектура MPAM задаёт направление, в котором память станет управляемым ресурсом с общим интерфейсом resctrl, а практическую работу уже сейчас держат PMU мешей, контроллеров памяти и SPE. Команда, которая выстроила замеры и изоляцию на этих инструментах, получит и пользу от MPAM, когда тот дойдёт до продакшена, потому что половина работы, а именно разметка партиций и дисциплина мониторинга, уже будет сделана.