Железо и архитектуры Выбор железа под задачу: как читать характеристики
0%

Выбор железа под задачу: как читать характеристики

Выбор железа под задачу: как читать характеристики

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

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

Отсюда единственный работающий порядок действий, вокруг которого построена глава:

1. Охарактеризовать нагрузку        → в какое узкое место она упирается СЕЙЧАС
2. Проверить, что упор настоящий    → не лечится ли он дешевле в коде
3. Прочитать спецификацию под ЭТО   → какие строки вообще относятся к вашему упору
4. Поставить эксперимент            → своя нагрузка, свои данные, повторы и статистика
5. Посчитать в правильных единицах  → на стоимость, на ватт, на стойку, по перцентилям

Пункты 3–5 без пунктов 1–2 дают ровно тот разговор, где сравнивают гигагерцы и количество ядер.

Шаг первый: характеризовать нагрузку, а не железо

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

Класс Что реально ограничивает Признак в измерении Что помогает в железе Что чинится в коде
Счёт заняты исполнительные блоки высокий IPC, высокая доля полезно завершённых тактов больше ядер, шире векторы, ускоритель другой алгоритм, векторизация, меньшая точность
Латентность памяти зависимая цепочка обращений низкий IPC, промахи последнего уровня, мало параллельных промахов память с меньшей задержкой, ближе узел NUMA смена структуры данных, непрерывность вместо указателей
Пропускная способность памяти шина насыщена трафик к DRAM у измеренного потолка, лишние потоки не ускоряют больше каналов, быстрее память, второй сокет блокировка по кэшу, сжатие, меньшая разрядность
Ветвления и фронтенд конвейер пуст высокая доля ошибок предсказания или простой фронтенда практически ничего убрать ветвления по данным, ужать горячий код
Ввод-вывод накопителя ожидание диска ядра простаивают, очередь длинная локальные NVMe вместо сетевого тома батчинг, асинхронный ввод-вывод, другой формат
Сеть ожидание ответа ядра простаивают, время в ожидании сокета ближе разместить, быстрее интерфейс меньше обращений, конвейеризация
Синхронизация потоки ждут друг друга время растёт с числом потоков меньше сокетов, ядра в одном кластере меньше общего состояния, шардирование

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

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

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

Отношение операций к байтам, оно же roofline. Возьмите число полезных операций за проход и число байтов, реально перегнанных между DRAM и ядром. Их частное — арифметическая интенсивность. Сравните её с точкой перелома платформы: пиковая скорость счёта, делённая на измеренную пропускную способность памяти. Левее перелома вы ограничены памятью, и покупка ядер не даст ничего. Модель предложена в работе Williams, Waterman, Patterson Roofline: An Insightful Visual Performance Model.

Модель roofline: точка перелома платформы, ограничение памятью слева и счётом справа

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

время выросло пропорционально снижению частоты ядра  → упор в счёт
время почти не изменилось от частоты ядра            → упор в память, ввод-вывод или сеть
время выросло от ухудшения памяти                    → упор в память
не изменилось ни от того, ни от другого              → упор в ожидание: диск, сеть, блокировки

Top-Down. Методика раскладывает каждый такт по четырём корзинам: ограничение фронтендом (нечего исполнять), неверная спекуляция, ограничение бэкендом (есть что исполнять, но нельзя) и полезная работа; дальше каждая корзина раскрывается вглубь. Это самый честный способ понять, где теряются такты на внеочередном ядре, где обычные счётчики простоев приблизительны — почему именно, разобрано в главах конвейер и суперскалярность и внеочередное исполнение. Практика применения — в профилировании CPU.

Шаг второй: читаем спецификацию по строкам

Частота: базовая, максимальная и та, что будет у вас

Базовая частота — обязательство: при заявленных условиях охлаждения все ядра выдержат её неограниченно долго. Максимальная — разрешение: ядро может до неё подняться, если сложатся условия. Условия складываются редко.

Максимум обычно достижим одним-двумя ядрами: когда работают все, устойчивая частота ниже, и разрыв между одноядерной и всеядерной частотой для настольных и серверных ядер поколений примерно 2018–2024 годов — порядок 15–30 процентов, а на многоядерных серверных кристаллах больше. Ограничений минимум три, и срабатывает самое строгое: температура кристалла, мгновенный ток через цепи питания и усреднённая по окну мощность пакета — разбор в главе мощность и пределы. Тип инструкций влияет на частоту: широкие векторные и матричные операции переключают больше вентилей за такт, поэтому на них ядро работает ниже, и векторизация маленького участка изредка замедляет программу целиком. Одинаковые изделия работают по-разному: разброс между экземплярами одной модели — следствие производственного разброса и сортировки, см. главу производство.

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

Ядра: сколько их и какие они

Разнородные ядра. Кристалл часто содержит ядра двух типов: широкие производительные и компактные энергоэффективные, которых на ту же площадь помещается несколько. Для пакетной нагрузки суммарная пропускная способность растёт; для чувствительной к задержке попадание потока на энергоэффективное ядро означает разовое замедление в разы. Планировщик обычно справляется, но поток с жёстким требованием по времени отклика привязывайте явно.

SMT, она же одновременная многопоточность. Два логических потока делят один набор исполнительных блоков, один L1 и один L2. Модель простая: SMT заполняет дыры, которые оставляет один поток.

Когда SMT выигрывает Когда SMT вреден
поток часто простаивает на промахах памяти — второй занимает освободившиеся блоки нагрузка уже насыщает исполнительные блоки: два потока делят одно и то же
много независимых коротких задач, важна суммарная пропускная способность важна задержка отдельного запроса: сосед вносит непредсказуемые паузы
рабочие множества обоих потоков помещаются в приватный кэш рабочие множества конкурируют за один L2 и вытесняют друг друга
лицензирование считается по физическим ядрам лицензирование по логическим потокам при выигрыше в 10–20 процентов

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

Кэши: почему «мегабайты L3» сами по себе ничего не значат

Механика разобрана в главе подсистема памяти; здесь только то, что нужно для чтения спецификации.

Уровни различаются не только объёмом, но и принадлежностью: L1 и обычно L2 приватные, поэтому суммарный объём надо делить на ядра — потоку доступен только свой; L3 общий, и делят его все активные ядра, а на многих платформах ещё ускорители и сетевые устройства. Единственное осмысленное сравнение — с рабочим множеством: если горячие данные потока занимают 200 МБ, разница между 16 и 64 МБ общего кэша не меняет ничего, а если данные занимают 1,5 МБ на поток и потоков шестнадцать — те же 24 МБ решают всё. Считайте долю, а не сумму: полезная величина — мегабайты последнего уровня на активное ядро, и кристалл с большим L3 при вдвое большем числе ядер даёт меньше кэша на поток. Топология влияет сильнее, чем кажется: на больших кристаллах последний уровень разрезан на срезы, латентность зависит от расстояния до нужного среза, а кластерная организация ядер делает часть кэша ближе, часть дальше — из спецификации это не читается.

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

Память: каналы, скорость, тайминги, ёмкость, NUMA

Пиковая пропускная способность считается арифметически: число каналов, умноженное на разрядность канала и на скорость передачи. Порядок величины для восьмиканальной серверной платформы начала 2020-х — сотни гигабайт в секунду на сокет, для двухканальной настольной — десятки. Реально достижимая на потоковом тесте обычно 60–85 процентов от пика: часть тактов уходит на переключение банков, обновление и протокол.

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

NUMA стоит отдельно. На многосокетной машине, а на некоторых кристаллах и внутри сокета, память разбита на узлы; доступ к чужому узлу дороже локального в 1,5–2 раза и вдобавок отъедает межсокетную шину. Без привязки потоков и памяти многопоточная программа на двух сокетах регулярно работает медленнее, чем на одном с половиной ядер: ровно тот случай, когда «больше железа» без настройки даёт минус.

Ввод-вывод: линии PCIe и где на самом деле узкое место

Пропускная способность одной линии примерно удваивается с каждым поколением: порядок величины — около 1 ГБ/с в одну сторону для третьего поколения, около 2 для четвёртого, около 4 для пятого. Типичный NVMe получает четыре линии, ускоритель — шестнадцать. Считать надо не только линии, но и то, кто с кем их делит.

Линий у платформы конечное число: два ускорителя по шестнадцать линий, четыре NVMe и сетевая карта на 100 Гбит/с могут вместе не поместиться, и часть устройств получит половинную ширину — в спецификации платы это пишут мелким шрифтом. Поколение определяется самым слабым звеном: устройство пятого поколения в слоте третьего работает по третьему. Для ускорителя копирование часто дороже счёта: шестнадцать линий четвёртого поколения дают десятки гигабайт в секунду, внутренняя память ускорителя — на порядок больше, отсюда правило из главы ускорители: выигрыш считается вместе со стоимостью передачи туда и обратно. Для NVMe узкое место обычно не в линиях: четыре линии четвёртого поколения перекрывают потолок почти любого одиночного накопителя по последовательному чтению, а ограничивают глубина очереди, файловая система и характер обращений.

TDP: это тепловой проект, а не измеренная мощность

TDP — величина, на которую рассчитывают систему охлаждения, чтобы изделие работало в заявленном режиме. Это не потребление и не максимум: кратковременный пик заметно выше, средняя мощность на лёгкой нагрузке заметно ниже, а правила определения TDP у разных производителей разные, поэтому цифры между семействами несравнимы. Для планирования нужна измеренная мощность под вашей нагрузкой — счётчики энергии пакета, показания блока питания или PDU — и понимание, что охлаждение и настройки платформы определяют устойчивый режим не меньше самого кристалла: одинаковые изделия в плотном и просторном корпусе покажут разную устойчивую частоту. Физика этого — в главе мощность и пределы.

Наборы команд и специализированные блоки

Строка «поддерживает такой-то векторный набор» означает ровно одно: инструкции не вызовут исключения. Ускорения она не означает. Чтобы наличие превратилось в производительность, нужно одно из трёх: пересборка с указанием целевой архитектуры, диспетчеризация во время выполнения (несколько версий функции и выбор по опросу возможностей) или библиотека, которая делает это за вас. Если дистрибутив собран под базовый уровень архитектуры ради совместимости, расширения простаивают; пределы автовекторизации — в главе SIMD и векторы. То же с матричными умножителями, аппаратным шифрованием, кодеками и блоками нейросетевого вывода: они дают выигрыш в разы и десятки раз, но только внутри своего узкого класса и только через конкретный программный путь. Вопрос при чтении спецификации — не «есть ли блок», а «пойдёт ли моя задача через него и какой библиотекой».

Шаг третий: почему чужие бенчмарки не отвечают на ваш вопрос

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

  1. Другой компилятор и флаги. Разница между сборкой под базовый уровень архитектуры и сборкой под конкретную микроархитектуру на счётном коде — десятки процентов.
  2. Другие версии библиотек. В линейной алгебре, сжатии, криптографии и регулярных выражениях производительность определяет библиотека, а не процессор.
  3. Геометрическое среднее по неродственным тестам. Сводный балл смешивает нагрузки, из которых к вам относится в лучшем случае одна: среднее по «сжатие, шахматы, рендеринг, компиляция» не предсказывает поведение вашего сервиса ни в какую сторону.
  4. Другое охлаждение и другая платформа. Устойчивая частота под долгой нагрузкой зависит от корпуса, радиатора, температуры в помещении и настроек платы.
  5. Другой объём данных. Тест с рабочим множеством внутри кэша и ваша задача с десятками гигабайт — разные классы нагрузки, а не «то же самое, но больше».
  6. Другое ядро ОС и другой набор смягчений уязвимостей. Накладные расходы защиты от спекулятивных атак для системно-нагруженного кода измеряются процентами и десятками процентов (см. предсказание переходов).

Как выглядит честный эксперимент

Что это значит практически. Прогрев: первые прогоны измеряют не программу, а холодные кэши, ленивую загрузку страниц, JIT-компиляцию и раскрутку файлового кэша, поэтому их выбрасывают. Повторы и разброс: один прогон — не измерение, нужны десятки, а отчитываться надо медианой и перцентилями, потому что распределение времени почти всегда несимметрично с длинным правым хвостом. Фиксация частоты: динамическое управление частотой — главный источник невоспроизводимости, поэтому для сравнения вариантов кода частоту фиксируют, а для оценки реальной производительности, наоборот, оставляют боевую и измеряют дольше. Изоляция: привязка к конкретным ядрам, вынос прочих задач, отключение SMT на время эксперимента, остановка фоновых задач системы. Одинаковые данные: не синтетика, а срез реального ввода вместе с его перекосом — распределение размеров, доля попаданий в кэш приложения, доля тяжёлых запросов.

Методика целиком — в треке производительности: измерения и бенчмаркинг; как встроить эксперимент в цикл работы — в рабочем процессе оптимизации.

Шаг четвёртый: метрики, в которых считать

Абсолютная производительность почти никогда не является критерием выбора. Критерии — отношения.

Метрика Когда решающая Подвох
Производительность на единицу стоимости закупка и аренда под известный объём работы стоимость владения включает питание, охлаждение, место, обслуживание и людей
Производительность на ватт плотные стойки, батарейные устройства, лимит подводимой мощности зависит от рабочей точки: то же изделие на пониженной частоте заметно эффективнее
Производительность на юнит стойки дата-центр с ограничением по площади или киловаттам ограничение чаще по мощности на стойку, чем по месту
Задержка в перцентилях всё интерактивное среднее скрывает хвост, p99 отличается от медианы в разы
Пропускная способность при заданной задержке сервисы с целевым SLO максимум пропускной способности достигается при уже неприемлемой задержке

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

Практика: инвентаризация и измерение на Linux

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

## Модель, число ядер и потоков, узлы NUMA, поддерживаемые расширения набора команд
lscpu
## Размеры и уровни кэшей так, как их сообщает ядро; смотреть на строку последнего уровня
lscpu --caches

## Полная топология одной картинкой: сокеты, узлы NUMA, кэши, ядра, потоки,
## привязка устройств PCIe к узлам. Пакет hwloc
lstopo-no-graphics --of console
## Узлы NUMA, объём памяти на узле, матрица относительных расстояний между узлами
numactl --hardware

## Сколько модулей памяти, в каких слотах и какой скорости.
## Половина пустых каналов, то есть половина полосы, видна именно здесь
sudo dmidecode -t memory | grep -E 'Locator|Size|Speed|Rank'
## Устройства и подключение: заявленные и фактические ширина и поколение линий
lspci -vv | grep -E 'LnkCap|LnkSta'
## Режим управления частотой, доступные ступени, включён ли turbo
cpupower frequency-info

Дальше измерение: сначала общая картина по счётчикам, потом раскладка тактов, потом микробенчмарки подсистем.

## Общая картина. IPC около 2 и выше — счётная нагрузка, 0,3–0,5 — ожидание памяти
perf stat -e cycles,instructions,cache-references,cache-misses,branch-misses ./prog
## Раскладка тактов по четырём корзинам Top-Down: Retiring — полезная работа,
## Backend Bound — данные и ресурсы, Frontend Bound — подача команд, Bad Speculation — переходы
perf stat -M TopdownL1 ./prog
## Куда уходит время внутри программы, с разбивкой по функциям
perf record -g ./prog && perf report --stdio

## Пропускная способность памяти потоковым тестом в духе STREAM.
## Сравнивать надо не с паспортным пиком, а вот с этим числом
./stream
## Подсистемы по отдельности
sysbench cpu --cpu-max-prime=20000 --threads=8 run
sysbench memory --memory-block-size=1M --memory-total-size=64G --threads=8 run

И отдельно — режим повторяемости, без которого измерения гуляют на десятки процентов.

## Отключить turbo для повторяемых сравнений вариантов кода; путь зависит от драйвера частоты
echo 1 | sudo tee /sys/devices/system/cpu/intel_pstate/no_turbo
sudo cpupower frequency-set --governor performance
## Привязать процесс к конкретным ядрам, а память — к их узлу NUMA
taskset -c 2,3 numactl --membind=0 ./prog
## Политика реального времени, чтобы фоновые задачи не мешали.
## Осторожно: зависший процесс с такой политикой способен подвесить систему
sudo chrt --fifo 50 ./prog
## Изоляция ядер от планировщика задаётся при загрузке: параметры isolcpus и nohz_full
cat /proc/cmdline

Как читать результат в двух словах: высокий Retiring — нагрузка счётная, помогут ядра и векторы; высокий Backend Bound вместе с промахами последнего уровня — нагрузка на память, помогут каналы, кэш и локальность; высокий Frontend Bound — раздутый горячий код; высокий Bad Speculation — непредсказуемые ветвления, и железо тут не поможет.

Калькулятор выбора: где будет узкое место и что выгоднее

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

"""Узкое место и стоимость работы: roofline плюс закон Амдала.

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

Сложность: O(M * log C) по времени, где M — число машин, C — число ядер
(перебираются степени двойки), и O(M) по памяти. Считается мгновенно, поэтому
модель удобно гонять по сетке параметров, а не по одному варианту.
"""
from dataclasses import dataclass

CACHE_SPEEDUP = 5.0   # во сколько раз кэш «дешевле» DRAM по эффективной полосе


@dataclass(frozen=True)
class Workload:
    name: str
    ops_total: float          # условных операций на единицу работы
    intensity: float          # операций на байт трафика к DRAM
    working_set: float        # байт горячих данных на один поток
    parallel_fraction: float  # доля работы, которая распараллеливается


@dataclass(frozen=True)
class Machine:
    name: str
    cores: int
    ops_per_core: float       # пиковых операций в секунду на ядро
    mem_bw: float             # байт в секунду, ИЗМЕРЕННАЯ, а не паспортная
    llc: float                # байт общего кэша последнего уровня
    price_per_hour: float     # полная стоимость владения, условных единиц
    watts: float              # измеренное потребление под нагрузкой


def attainable(w: Workload, m: Machine, threads: int) -> tuple[float, str]:
    """Достижимая скорость счёта и имя узкого места при заданном числе потоков."""
    compute_roof = m.ops_per_core * threads
    # если рабочие множества всех активных потоков влезают в общий кэш,
    # трафик к DRAM резко падает, а эффективная полоса растёт
    fits = w.working_set * threads <= m.llc
    memory_roof = m.mem_bw * (CACHE_SPEEDUP if fits else 1.0) * w.intensity
    if compute_roof <= memory_roof:
        return compute_roof, "счёт"
    return memory_roof, "память в кэше" if fits else "память в DRAM"


def amdahl(fraction: float, threads: int) -> float:
    """Во сколько раз ускорится работа при данной доле параллельной части."""
    return 1.0 / ((1.0 - fraction) + fraction / threads)


def evaluate(w: Workload, m: Machine) -> dict:
    """Перебор степеней двойки по числу потоков; лучший режим по стоимости."""
    best, threads = None, 1
    while threads <= m.cores:
        rate, limit = attainable(w, m, threads)
        # скорость ограничена и потолком платформы, и последовательной частью
        scaled = min(rate, m.ops_per_core * amdahl(w.parallel_fraction, threads))
        seconds = w.ops_total / scaled
        cand = {"машина": m.name, "потоков": threads, "узкое место": limit,
                "секунд": round(seconds, 3),
                "стоимость": round(seconds * m.price_per_hour / 3600.0, 6),
                "ватт-часов": round(seconds * m.watts / 3600.0, 4)}
        if best is None or cand["стоимость"] < best["стоимость"]:
            best = cand
        threads *= 2
    return best


LOADS = [Workload("потоковая агрегация", 2.0e12, 0.5, 512e6, 0.97),
         Workload("плотная линейная алгебра", 2.0e12, 24.0, 6e6, 0.99)]
FLEET = [Machine("широкий узел, много ядер", 64, 3.0e10, 180e9, 128e6, 4.0, 400),
         Machine("узкий узел, быстрая память", 16, 3.6e10, 140e9, 64e6, 1.6, 180),
         Machine("два узких узла рядом", 32, 3.6e10, 280e9, 128e6, 3.2, 360)]

for load in LOADS:
    print(f"=== {load.name} ===")
    for machine in FLEET:
        print("   ", evaluate(load, machine))

Чему учит структура расчёта, независимо от подставленных чисел. Для нагрузки с низкой интенсивностью узкое место — память на всех конфигурациях, и выигрывает та, где больше измеренной полосы на единицу стоимости, а не та, где больше ядер: ядра сверх точки насыщения полосы бесплатны по производительности, но не по счёту. Для нагрузки с высокой интенсивностью и малым рабочим множеством узкое место — счёт, и ядра работают, пока не упрутся в последовательную часть: при доле параллельной части 0,97 потолок ускорения — примерно 33 раза, сколько ядер ни добавляй. А замена одной большой машины двумя меньшими часто выигрывает по полосе памяти и проигрывает по задержке и цене синхронизации, если нагрузке нужно общее состояние (см. многоядерность). Модель полезна ровно до момента, когда вы поставите настоящий эксперимент; её задача — сократить список конфигураций, которые вообще стоит проверять.

Дерево решения: что менять, когда упёрлись

Симптом, железо и более дешёвая починка в коде

Симптом в профиле Что менять в железе Что почти всегда дешевле сделать в коде
Низкий IPC, промахи последнего уровня, мало параллельных промахов память с меньшей задержкой, привязка к локальному узлу NUMA непрерывный массив вместо графа указателей, укрупнить элементы
Трафик к DRAM на уровне измеренного потолка больше каналов памяти, второй сокет, другая платформа блокировка по кэшу, обработка на месте, меньшая разрядность чисел
Высокий Retiring, ядра загружены полностью больше ядер, шире векторы, ускоритель другой алгоритм, векторизация, снижение точности вычислений
Высокий Frontend Bound практически ничего ужать горячий код, оптимизация раскладки по профилю, меньше инлайна
Высокий Bad Speculation практически ничего убрать ветвления по данным, отсортировать вход, перейти к маскам
Время растёт с числом потоков меньше сокетов, ядра в одном кластере убрать ложное разделение, шардировать состояние, реже брать блокировки
Ядра простаивают, очередь к диску длинная локальные NVMe вместо сетевого тома батчинг, асинхронный ввод-вывод, колоночный формат
Ядра простаивают, время в ожидании сети ближе разместить, быстрее интерфейс объединить обращения, конвейеризовать, кэшировать у клиента
Устойчивая частота ниже ожидаемой под долгой нагрузкой охлаждение, корпус, лимиты платформы размазать нагрузку во времени, снизить пиковую векторную ширину

Облако против своего железа: по каким параметрам сравнивать честно

Сравнение «цена инстанса против цены сервера» неинформативно: это разные товары. Честное сравнение делается по списку различий, каждое из которых меняет либо стоимость, либо достижимую производительность.

Параметр Арендованный виртуальный ресурс Физический сервер
Что такое одна вычислительная единица обычно логический поток SMT физическое ядро целиком
Изменчивость производительности соседи делят последний уровень кэша, полосу памяти и ввод-вывод предсказуемо, вся машина ваша
Мелкие конфигурации часто с накоплением кредитов ЦП: после исчерпания скорость режется в разы таких режимов нет
Дисковая подсистема сетевой том: задержка на порядок выше локального NVMe, есть лимит операций локальные NVMe с полной задержкой и полосой
Исходящий трафик тарифицируется отдельно и на объёме становится крупной статьёй оплачивается канал, а не байты
Скорость изменения ёмкости и цена простоя минуты; платите за включённое недели и месяцы; платите всегда
Обслуживание, замена дисков, гарантия входит в цену ваша задача и ваши люди

Отсюда правила без идеологии. Нестабильная и растущая нагрузка, короткие пики, ранняя стадия продукта — аренда почти всегда выигрывает, потому что вы платите за неопределённость, а не за железо. Стабильная круглосуточная нагрузка известного объёма, живущая годами, — физическое железо начинает выигрывать, и разрыв тем больше, чем выше утилизация; точка безубыточности считается по полной стоимости владения, включая людей и место. Нагрузка, чувствительная к хвосту задержек, — «шумный сосед» бьёт именно по p99, и на выделенном железе разброс существенно ниже, что часто важнее средней цены. Много исходящего трафика — считайте его отдельной строкой до, а не после переезда. Случайный доступ к диску — сетевой том и локальный NVMe различаются по задержке на порядок, и это архитектурное различие, а не настройка. Подробный разбор — в статьях VPS, VDS и выделенные серверы и стоимость облака и компромиссы.

Встраиваемые и батарейные устройства: другая целевая функция

Здесь метрика выбора меняется полностью: пиковая производительность почти не важна.

Энергия на операцию. Устройство просыпается, делает работу и засыпает, поэтому считать надо джоули на завершённую операцию, а не ватты: быстрое ядро, которое успевает досчитать и уснуть, регулярно экономичнее медленного, которое считает вдвое дольше на вдвое меньшей мощности. Ток покоя. Для устройства, которое 99 процентов времени спит, режимы сна и токи утечки важнее всей остальной спецификации вместе взятой: разница между микроамперами и десятками микроампер в глубоком сне — это разница между годом и месяцем работы от одного элемента. Детерминированность. Для управления двигателем или радиотрактом важна гарантированная верхняя граница времени реакции, а не средняя скорость, поэтому здесь сознательно выбирают неглубокий конвейер, отсутствие внеочередного исполнения и память с предсказуемым временем доступа, а кэш иногда отключают ради устранения разброса. Периферия и интеграция. Часто выбор определяет не ядро, а наличие нужных интерфейсов, аналоговых блоков и радиочасти на одном кристалле: это экономит плату, мощность и надёжность.

Арифметика этого разобрана в энергии и ограничениях встраиваемых систем, физическая сторона — в главе мощность и пределы.

Чек-лист выбора железа

  1. Сформулирована ли цель числом: пропускная способность при заданной задержке, срок расчёта, стоимость единицы работы, время автономной работы.
  2. Определён ли класс узкого места измерением, а не рассуждением.
  3. Проверено ли, что узкое место не лечится в коде дешевле, чем покупкой.
  4. Известно ли рабочее множество в байтах и как оно соотносится с уровнями кэша.
  5. Измерена ли реальная пропускная способность памяти, а не взята паспортная, и посчитана ли арифметическая интенсивность относительно точки перелома.
  6. Учтены ли каналы памяти и физическая раскладка модулей, а не только суммарный объём.
  7. Проверено ли, хватает ли линий PCIe всем устройствам одновременно и в нужном поколении.
  8. Понятно ли, что даёт SMT именно вашей нагрузке, и по чему считается лицензирование.
  9. Проверена ли устойчивая частота под долгой нагрузкой в целевом корпусе и охлаждении.
  10. Измерено ли реальное потребление вместо того, чтобы взять TDP.
  11. Собрана ли программа под целевую архитектуру и используются ли доступные расширения.
  12. Есть ли прогон вашей нагрузки на вашей выборке данных — с прогревом, повторами и разбросом.
  13. Считаются ли задержки в перцентилях, а не в среднем, и посчитана ли полная стоимость владения: питание, место, трафик, обслуживание, люди.

Типичные заблуждения

  • «Больше гигагерц — быстрее». Частота — один из трёх множителей времени исполнения (см. обзор трека), и она динамическая. Для нагрузки, упирающейся в память, её повышение не даёт почти ничего; для счётной даёт, но покупается непропорциональным ростом мощности.
  • «Больше ядер — быстрее». Прирост ограничен долей параллельной части, полосой памяти и стоимостью синхронизации. Есть нагрузки, которые на большем числе ядер работают медленнее — из-за конкуренции за общий кэш и полосу и из-за роста цены согласования.
  • «Возьмём побольше кэша». Кэш даёт эффект скачком, когда рабочее множество в него влезает, и почти не даёт, когда не влезает. Сравнивать надо не мегабайты, а мегабайты на активный поток против размера ваших горячих данных.
  • «Купим ускоритель». Ускоритель окупается, если работы много, она однотипная и стоимость передачи данных меньше выигрыша от счёта. Нарушение любого условия делает универсальное ядро быстрее (ускорители).
  • «Облако дешевле» и «облако дороже». Обе фразы бессодержательны без утилизации, профиля нагрузки, объёма трафика и стоимости людей: один и тот же сервис при 15 и при 80 процентах утилизации даёт противоположные ответы.
  • «В спецификации написана производительность». Написаны пределы. Достижимая доля от предела — отдельная величина, и определяет её код.
  • «Синтетический тест показал прирост — значит будет прирост». Он показал прирост на своей арифметической интенсивности, своём рабочем множестве и своей библиотеке. Совпадение с вашей нагрузкой надо доказывать, а не предполагать.
  • «У изделий одной модели одинаковая производительность». Разброс между экземплярами, корпусами и версиями микрокода реален и измеряется процентами, а иногда десятками процентов.

Мини-итог

  • Выбор начинается не со спецификации, а с классификации нагрузки по узкому месту; классов семь, и в пяти из них код чинится дешевле железа. Roofline отвечает на главный вопрос: левее точки перелома платформы бесполезно покупать счёт, правее — бесполезно покупать память.
  • Каждая строка спецификации описывает предел при идеальных условиях: частота — при одном активном ядре, полоса — при идеальном потоке, TDP — тепловой проект, набор команд — лишь отсутствие исключения при исполнении.
  • Чужие бенчмарки отвечают на чужой вопрос. Силу имеет одно измерение: ваша нагрузка на ваших данных, с прогревом, повторами и статистикой.
  • Решают отношения, а не абсолютные числа: на стоимость, на ватт, на стойку, и задержка в перцентилях, а не в среднем. Облако и своё железо — разные товары; сравнение делается по списку различий и по полной стоимости владения при вашей реальной утилизации.
  • Для батарейных устройств целевая функция другая: джоули на операцию, ток покоя и гарантированная верхняя граница времени реакции.

Источники

Что дальше

На этом трек закончен. Мы начали с трёх смыслов слова «архитектура» и уравнения времени исполнения, прошли путь от транзистора и вентиля к системе команд как контракту, разобрали, как конвейер, суперскалярность, предсказание переходов и иерархия памяти превращают контракт в производительность, увидели, где заканчивается универсальное ядро и начинаются векторы, многоядерность и специализированные устройства, и уперлись в физику: бюджет ватт, тепло, площадь кристалла и выход годных. Эта глава замкнула круг — те же самые механизмы вернулись в виде строк спецификации, которые теперь читаются не как реклама, а как список пределов с понятным происхождением. Главный навык, который трек пытался передать, — привычка задавать вопрос «чем это ограничено физически» раньше вопроса «что тут можно подкрутить».

Куда идти дальше, зависит от того, что вы делаете с этим знанием:

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

Нашли неточность? Выделите фрагмент текста — рядом появится жучок.

Нужен разбор именно вашей ситуации?

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

Доска запросов