Полоса памяти в сервере делится между ядрами, контроллерами и сокетами, и её нехватка выглядит странно: процессор не загружен, а всё тормозит. Диагноз ставится счётчиками uncore: Intel PCM показывает загрузку каналов памяти и линков UPI в гигабайтах в секунду, perf читает те же счётчики без внешних утилит, а STREAM и perf c2c дают реальную полосу нагрузки. Ниже разобраны расчёт пика по конфигурации DDR5, чтение счётчиков каналов, локальная и удалённая полоса на NUMA, полоса UPI между сокетами и типичные ловушки замеров.

Расчёт пиковой полосы по конфигурации DDR5

Пиковая полоса это произведение числа каналов на полосу канала, а полоса канала равна частоте передачи умноженной на ширину. DDR5 передаёт по 64 бита данных на канал с удвоенной эффективной частотой относительно ядра такта. Формула для одного канала: частота MT/s умножить на 8 байт и разделить на тысячу для гигабайт в секунду.

Конкретные числа: DDR5-4800 даёт 38.4 ГБ/с на канал, DDR5-5600 даёт 44.8, DDR5-6400 даёт 51.2. Сокет Sapphire Rapids с восемью каналами DDR5-4800 имеет пик 307.2 ГБ/с, с DDR5-6400 поднимается до 409.6. Emerald Rapids с двенадцатью каналами на DDR5-5600 даёт 537.6 ГБ/с. На двухсокетной машине пик суммируется, но полоса между сокетами ограничена UPI и это отдельная история.

Реальная полоса STREAM Triad стабильно составляет 85-90 процента пика на Intel из-за эффективности контроллеров и чередования. Если замер даёт сильно ниже 80 процентов, ищут неполное заполнение каналов, неверную раскладку DIMM по слотам или узкое место в NUMA-привязке бенчмарка. Заполнение каналов проверяется в спецификации платы: пустые слоты снижают число активных каналов, и полоса падает кратно.

Для сравнения поколений полезно держать в голове DDR4-3200: 25.6 ГБ/с на канал, восемь каналов Sapphire Rapids с DDR4 дают 204.8 ГБ/с на сокет, что на треть ниже DDR5-4800. Многие сервера 4-го поколения Xeon собирают именно с DDR4, и ожидать там 300 ГБ/с не стоит. Отдельная ветка, процессоры Sapphire Rapids с HBM: на сокете стоит 64 ГиБ HBM2e с полосой порядка терабайта в секунду, и прошивка может настраивать режим Flat, где HBM виден как отдельный NUMA-узел, или Cache, где он работает кешем перед DDR5. В режиме Flat полоса обычной памяти остаётся DDR5, а горячие данные кладут на HBM-узел через numactl, и замеры ведут отдельно для обоих узлов.

Раскладка DIMM по слотам имеет правила, и их нарушение кратко бьёт по полосе. Один DIMM на канал, 1DPC, даёт полный пик канала. Два DIMM на канал, 2DPC, при тяжёлых модулях снижает эффективную частоту канала, и плата может упасть с 5600 до 4800 на канале. Чередование по каналам включается в BIOS настройкой Memory Interleaving, и его выключение локализует трафик, что иногда полезно для изоляции, но почти всегда снижает пиковую полосу при случайных нагрузках.

Полезная опция на больших серверах, Sub-NUMA Clustering, SNC, делит сокет на два-четыре домена со своими долями кеша и контроллеров памяти. Включение SNC укорачивает путь памяти внутри домена и повышает локальную полосу и латентность, но уменьшает объём локальной памяти домена. Для NUMA-дружелюбных нагрузок SNC=2 даёт плюс к локальной полосе, для нагрузок с общими данными между всеми ядрами сокета наоборот вредит, и решение принимается по профилю доступа.

Замеры через Intel PCM и чтение каналов

PCM, Performance Counter Monitor, читает счётчики uncore без пересчётов. Утилита pcm-memory.x печатает полосу по каналам каждые N секунд:

pcm-memory.x 1

Колонки показывают Read и Write гигабайт в секунду на каждый канал и по сокетам, а суммарные строки дают картину целиком. Для сервера Sapphire Rapids здоровый вид под STREAM: по каждому каналу около 30 ГБ/с чтения и суммарная полоса близкая к 90 процентам пика. Если пара каналов простаивает, а остальные перегружены, смотрят раскладку памяти по каналам и настройки чередования BIOS.

Инструмент pcm.iio показывает полосу на PCIe-устройствах и помогает понять, сколько съедают карты. Для сочетания трафика памяти и устройств полезен pcm-numa.x, он разлагает трафик по узлам и показывает Remote и Local компоненты.

Версии PCM собираются под поколения: свежие сборки поддерживают Sapphire Rapids и Emerald Rapids, старые нужны для Cascade Lake. Приложение PCM требует доступа к счётчикам MSRs, обычно запускается от суперпользователя, и конфликтует с perf за uncore счётчики, поэтому их не смешивают в одном замере.

perf с событиями uncore IMC

Ту же информацию даёт perf без внешних утилит. Список событий uncore:

perf list | grep -i -E "uncore_imc|cas"

Ключевые события UNC_M_CAS_COUNT.RD и UNC_M_CAS_COUNT.WR считают обращения к каналам, UNC_M_ACT_COUNT.RD считает активации строк, полезные при большом числе случайных доступов. Замер системно:

perf stat -a -e UNC_M_CAS_COUNT.RD,UNC_M_CAS_COUNT.WR -- sleep 5

Числа событий пересчитываются в байты: CAS Read на канал означает 64 байта за событие, и умножение на частоту событий даёт гигабайты в секунду. Удобнее делить на время замера: события в секунду умножить на 64 и разделить на миллиард.

События привязаны к устройствам uncore_imc_<номер>, по одному на контроллер памяти, и ключом -C или через --per-socket их раскладывают по сокетам. На системах с высокой частотой событий perf слегка недосчитывает из-за мультиплексирования, поэтому для точных цифр события назначают явно и без мультиплекса, замеряя дольше.

Ряд событий дополняет картину по состоянию каналов. UNC_M_PRE_COUNT.PAGE_MISS считает предзарядки из-за промахов по рядам, и его рост на случайных нагрузках объясняет, почему реальная полоса ниже пиковой: каждая активация строки стоит времени, которое не переносит данные. Периодический refresh памяти отбирает несколько процентов полосы, и его вклад виден в сравнении замеров на пустом и нагруженном контроллере. Для каналов с исправлениями ECC счётчики ошибок показывают стабильность, и всплеск исправлений легко путают с перегрузкой полосы.

Полоса каждого ядра в L3 заметно выше полосы DRAM: одно ядро способно запросить из кеша десятки гигабайт в секунду, и восемь-десять активных ядер уже насыщают каналы DDR5. Отсюда практический вывод: насыщение полосы памяти начинается задолго до загрузки всех ядер, и приложения с потоком данных в десятки гигабайт в секунду стоит мерить не только по загрузке процессора, но и по счётчикам IMC. Ложное разделение кеш-линий усиливает трафик до DRAM через инвалидации, и связка с perf c2c позволяет отделить полосу, съеденную кеш-трафиком, от полезных потоков данных.

Отдельный класс событий - UNC_M_RPQ и UNC_M_WPQ, очереди чтения и записи контроллера. Их заполнение показывает, насколько буферы контроллера забиты запросами, и при переполнении новые обращения блокируются, что сразу бьёт по латентности. Нагрузка, держащая RPQ заполненным на 90% и выше, гарантированно страдает от задержек памяти, даже если полоса в ГБ/с не упирается в пик. Мониторинг этих счётчиков через perf stat -e UNC_M_RPQ_OCCUPANCY,UNC_M_WPQ_OCCUPANCY даёт раннее предупреждение о перегрузке контроллера до появления симптомов в приложении.

Локальная и удалённая полоса на NUMA

Полоса на NUMA-системе распадается на локальную и удалённую компоненты. Локальный трафик идёт через контроллеры своего сокета и упирается в их пик. Удалённый идёт через UPI и ограничен полосой линков: на Sapphire Rapids линк UPI 2.0 передаёт 20 гигатрансферов в секунду на 16 бит, что даёт около 40 ГБ/с в одну сторону на линк, и типовые платформы ставят два-четыре линка на сокет.

Практический тест: STREAM запускают numactl на узле 0 с памятью узла 0, затем с памятью узла 1. Разница между этими цифрами показывает цену удалённого доступа по полосе, обычно падение на 25-40 процентов на двусокетной системе. Если приложение в проде показывает смесь, её раскладывают через numactl --membind или interleave, и полоса выравнивается.

Счётчики UPI читаются PCM:

pcm-pcie.x 1

или отдельная утилита для UPI трафика в составе PCM показывает транзакции между сокетами. Рост UPI-полосы выше 80 процентов от линков под нагрузкой сигнализирует, что удалённый трафик стал узким местом, и тогда раскладка памяти важнее любого другого тюнинга.

Замер реальной полосы STREAM и типичные профили

Классический бенчмарк STREAM собирается просто и меряет четыре ядра: Copy, Scale, Add, Triad. Компиляция:

gcc -O3 -march=native -mcmodel=medium -fopenmp stream.c -o stream

Запуск на выделенных ядрах с привязкой:

numactl --cpunodebind=0 --membind=0 OMP_NUM_THREADS=24 ./stream

Триады на Sapphire Rapids дают порядка 270-280 ГБ/с на сокет с DDR5-4800, что соответствует 88 процентам пика 307. Значения ниже заставляют проверить: раскладку DIMM по каналам, NUMA-привязку бенчмарка, наличие фоновой нагрузки и энерголимиты BIOS, ведь контроллер памяти делится полосу с процессором при высоком потреблении ядер.

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

Типичные ошибки и диагностика полосы памяти

  1. замер на нечетном числе активных каналов, полоса падает кратно, проверка раскладки DIMM в документации платы и в выводе dmidecode -t memory;
  2. STREAM без NUMA-привязки, страницы размазаны по узлам, локальная и удалённая полоса смешиваются, запуск строго через numactl;
  3. одновременный запуск PCM и perf за uncore счётчики, один из инструментов получает мусор, замеры ведут по очереди;
  4. интерпретация суммарного трафика как полезной полосы, чтение и запись в кеш ядер не доходят до памяти, счётчики IMC показывают только обращения к DRAM;
  5. сравнение полосы без учёта энерголимитов, при высоком потреблении ядер контроллер памяти делится ресурсом, лимит мощности проверяют через pcm-power.x.
  6. игнорирование SNC на многосокетных серверах, без учёта доменов SNC трафик может уходить через межсокетные линки вместо локальных контроллеров, проверка BIOS и вывод pcm-numa.x;
  7. замеры на холодном кеше, первые проходы STREAM прогревают контроллеры, и первая итерация даёт заниженную полосу, прогреть 2-3 запуска перед фиксацией результатов;
  8. неучёт трафика PCIe, карты 100-400 Гбит/с могут съедать 10-20% полосы DRAM при DMA, pcm.iio и pcm-pcie.x помогают отделить память от устройств.

Диагностика опирается на связку инструментов: pcm-memory.x даёт живую картину по каналам, pcm-numa.x и pcm-pcie.x раскладывают трафик по узлам и устройствам, perf stat с UNC_M_CAS_COUNT даёт точные события без внешних зависимостей, STREAM на numactl фиксирует реальный потолок. dmidecode подтверждает заполнение каналов, а pcm-power проверяет энергоресурс.

Практический чек-лист настройки полосы перед запуском продакшн-нагрузки:
1) посчитать пик по конфигурации DIMM и DDR5-версии;
2) замерить STREAM на каждом NUMA-узле через numactl --cpunodebind --membind;
3) включить SNC=2 в BIOS для NUMA-дружелюбных задач или SNC=1 для общих данных;
4) запустить pcm-memory.x 1 под рабочей нагрузкой и найти каналы с загрузкой выше 85%;
5) если UPI-трафик выше 50% линка, перераспределить память через numactl --interleave или перенос подсистем;
6) зафиксировать конфигурацию в systemd через CPUAffinity и MemoryBind или в скрипте запуска numactl.

Полоса памяти редко бывает ограничением по умолчанию, но легко становится им после наращивания числа ядер и потоков. Честная методика короткая: посчитать пик по конфигурации, замерить STREAM на каждом узле, посмотреть живой профиль каналов под своей нагрузкой, найти дисбалансы каналов и удалённый трафик, и раскладкой памяти вернуть полосу к 90 процентам пика. Один такой вечер объясняет большинство загадочных тормозов на больших NUMA-серверах.

Для команд, встраивающих замеры в CI, полезен скрипт-обёртка, который запускает STREAM и PCM, парсит вывод и падает, если суммарная полоса Triad ниже 80% пика или если дисбаланс каналов больше 15%. Пример простого порога в bash: awk '/Triad/ { if ($2 < 0.8 * peak) exit 1 }' stream.log. Такой gate ловит регрессии раскладки DIMM, забытый SNC и забытый numactl до попадания в прод.