Тонкая настройка CPU для виртуалки: типы, флаги, привязка к ядрам
Виртуалка с базовыми настройками CPU работает — но не так быстро, как могла бы, и не так предсказуемо, как хотелось бы под нагрузкой. Разница между «типом CPU по умолчанию» и осознанно настроенным виртуальным процессором — это реальные проценты производительности на криптографии, векторных вычислениях и базах данных, и это предсказуемость задержек, которую не купишь одним только увеличением числа ядер. Разберём три рычага, которые есть у вас в руках: тип виртуального CPU, флаги конкретных возможностей и привязку виртуальных ядер к физическим — и когда каждый из них того стоит.
Содержание
- Тип CPU: host passthrough против эмуляции
- Host passthrough: максимум производительности, но с ловушкой миграции
- Эмулируемые типы CPU: переносимость важнее пиковой скорости
- Флаги CPU: включаем конкретные возможности вручную
- CPU pinning: привязка виртуальных ядер к физическим
- Как выбрать настройки под свой сценарий
Тип CPU: host passthrough против эмуляции
У гипервизора (в примерах — Proxmox/KVM, но принцип общий для любого QEMU-based решения) есть два принципиально разных подхода к тому, что видит виртуальная машина при обращении к процессору.
Host passthrough — виртуалка видит реальный набор возможностей физического CPU хоста напрямую, без прослойки эмуляции конкретных инструкций. Гипервизор просто «пробрасывает» то, что умеет железо.
Эмулируемый тип — виртуалка видит стандартизированный, консервативный профиль CPU (например, qemu64, kvm64, или конкретное поколение вроде Broadwell/Skylake-Client), который гарантированно работает на широком круге реального железа, но не обязательно включает всё, что умеет ваш конкретный процессор.
В Proxmox это настраивается одной строкой в конфиге VM:
# host passthrough — максимум возможностей физического CPU
qm set 101 --cpu host
# эмулируемый тип — переносимость
qm set 101 --cpu kvm64
В сыром libvirt/KVM (без Proxmox) то же самое выглядит в XML VM так:
<!-- host passthrough -->
<cpu mode='host-passthrough' check='none'/>
<!-- эмулируемый тип с явными флагами -->
<cpu mode='custom' match='exact' check='partial'>
<model fallback='allow'>Skylake-Client-IBRS</model>
</cpu>
Разница не в «лучше/хуже» — это компромисс между производительностью и переносимостью, и правильный выбор зависит от сценария, о котором ниже. Если вы только начинаете работу с виртуализацией и ещё не создавали первую VM, эти настройки логично изучать уже после того, как разобрались с базовой установкой Proxmox и первой виртуалкой.
Host passthrough: максимум производительности, но с ловушкой миграции
Плюс очевиден: виртуальная машина получает доступ ко всем инструкциям, кэшам и оптимизациям физического процессора хоста — от AES-NI до AVX-512, если хост это умеет. Для нагрузок, которые упираются в CPU (базы данных, шифрование трафика, сжатие, нейросети на CPU), разница с эмулированным профилем бывает ощутимой — конкретный процент зависит от вашей рабочей нагрузки и железа, и надёжнее его не заявлять заранее, а измерить на своём стенде до и после смены типа CPU.
Минус — это ловушка, которая обычно всплывает не сразу, а в момент, когда она наименее уместна: при попытке перенести или живо мигрировать виртуалку на другой физический хост с иным процессором. Виртуальная машина, «привыкшая» видеть конкретный набор инструкций исходного CPU, может отказаться корректно стартовать на хосте с другим (обычно — более старым или от другого вендора) набором возможностей. На практике это выглядит как зависание миграции, падение гостевой ОС в первые секунды после переключения, либо (в худшем случае) молчаливые сбои в приложениях, которые уже успели воспользоваться инструкцией, которой на новом хосте физически нет.
Важный нюанс: миграция между CPU разных вендоров (Intel → AMD или обратно) с host passthrough практически никогда не работает — и это не баг, а фундаментальное ограничение: у процессоров разных вендоров разные наборы базовых инструкций, и никакая эмуляция типа CPU внутри гипервизора это не сглаживает. Если у вас в кластере есть узлы на разных вендорах CPU, host passthrough для мигрируемых VM исключён по определению — подробнее о том, что вообще происходит с состоянием VM в момент переноса, разобрано в статье про живую миграцию виртуалок и память.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверЭмулируемые типы CPU: переносимость важнее пиковой скорости
Если у вас кластер из нескольких узлов и вы планируете (или уже практикуете) живую миграцию виртуалок между ними, эмулируемый тип CPU — не компромисс, а необходимость. Идея простая: гипервизор показывает виртуалке усечённый, но гарантированно одинаковый на всех узлах набор возможностей, и тогда миграция проходит без сюрпризов независимо от того, какой конкретно физический CPU стоит в каждом узле — при условии, что все узлы это подмножество поддерживают.
QEMU и Proxmox предлагают готовые уровни без ручного подбора флагов — семейство x86-64-v2/x86-64-v3/x86-64-v4 (обобщённые профили по уровню набора инструкций, а не привязка к конкретному поколению чипа) и профили конкретных поколений (Broadwell, Haswell, Skylake-Client, Cascadelake-Server и т.д.). Практический подход для разнородного кластера — взять профиль самого старого CPU в парке узлов: он станет "потолком" совместимости для всех.
# посмотреть, какие типы CPU вообще предлагает Proxmox
qm cpu-models
# явно указать конкретное поколение вместо host
qm set 101 --cpu Broadwell
Проверить, что виртуалка реально видит после смены типа, можно прямо изнутри гостевой ОС:
# внутри гостя (Linux)
lscpu
cat /proc/cpuinfo | grep flags
Если после смены типа CPU в выводе flags пропали инструкции, на которые рассчитывало приложение (например, AES-NI для шифрования) — это ожидаемо, это и есть цена переносимости. Дальше в игру вступают явные флаги.
Флаги CPU: включаем конкретные возможности вручную
Флаг — это конкретная возможность процессора (одна инструкция или группа связанных инструкций), которую можно явно включить или выключить для гостя независимо от базового типа CPU. Смысл в том, что даже эмулируемый, «урезанный» тип CPU можно точечно дополнить нужными флагами, не переходя на host passthrough целиком и не теряя переносимость по остальным параметрам.
Типичный пример — AES-NI (аппаратное ускорение AES-шифрования). Многие приложения, которые активно шифруют трафик или данные на диске (VPN-серверы, TLS-терминация, зашифрованные ФС), получают заметное ускорение именно за счёт этой инструкции. Если она физически поддерживается хостом, но не передана явно виртуалке (типичная ситуация с консервативным типом CPU вроде qemu64), приложение внутри гостя просто не увидит эту возможность и будет делать шифрование программно — медленнее.
В Proxmox флаги добавляются или убираются прямо в описании типа CPU через flags=:
# добавить AES-NI к типу kvm64
qm set 101 --cpu kvm64,flags=+aes
# добавить несколько флагов и одновременно один исключить
qm set 101 --cpu qemu64,flags=+aes;+pcid;-hypervisor
В libvirt XML то же самое явно, по одному флагу на строку — это удобнее, когда флагов много и хочется видеть их в конфиге явно, а не одной строкой:
<cpu mode='custom' match='exact' check='partial'>
<model fallback='allow'>qemu64</model>
<feature policy='require' name='aes'/>
<feature policy='require' name='pcid'/>
<feature policy='disable' name='hypervisor'/>
</cpu>
Другие флаги, которые практически имеет смысл проверять под конкретную нагрузку: векторные расширения (avx, avx2, avx512f — актуально для нагрузок с массовыми вычислениями, включая часть задач на CPU без GPU), pcid (снижает накладные расходы на переключение контекста, особенно заметно после патчей от Meltdown/Spectre), md-clear/spec-ctrl (сами по себе про безопасность, а не про скорость — но их отсутствие или неверная настройка иногда путают с проблемой производительности).
Практический способ проверки «а флаг вообще дошёл до гостя» — не гадать по документации, а посмотреть в /proc/cpuinfo внутри VM до и после изменения конфига (с перезагрузкой виртуалки, флаги CPU применяются только при полном рестарте, не при reboot изнутри гостя, а при остановке и старте VM гипервизором).
CPU pinning: привязка виртуальных ядер к физическим
По умолчанию планировщик хоста сам решает, на каком физическом ядре в конкретный момент времени исполняется виртуальное ядро (vCPU) вашей VM — и может менять это решение много раз в секунду, перебрасывая нагрузку между ядрами по своим соображениям балансировки. Для большинства сценариев это нормально и даже полезно — гибкость планировщика позволяет эффективно утилизировать общий пул ядер между разными VM.
CPU pinning — это явное закрепление: конкретное виртуальное ядро VM всегда исполняется на конкретном физическом ядре хоста, и планировщик его туда-сюда не переносит. Выигрыш — предсказуемость: устраняется «дрожание» производительности (jitter) от постоянных переключений между ядрами и, что часто важнее, от миграции между разными NUMA-узлами — переход vCPU на ядро в другом NUMA-узле означает более медленный доступ к памяти, и без привязки такие переходы происходят незаметно для вас, но заметно для задержек приложения.
Платите вы за это гибкостью: закреплённые за одной VM ядра хост уже не может динамически отдать другой виртуалке в момент простоя первой. На плотно утилизированном хосте это может означать, что часть вычислительной мощности физически простаивает, пока конкретная VM ею не пользуется — прямая противоположность идее оверкоммита CPU (где эта грань проходит без pinning, разобрано в статье про оверкоммит CPU и памяти).
В Proxmox (начиная с версии, где появилась настройка affinity в интерфейсе VM → Resources) привязка задаётся списком физических ядер:
# закрепить VM за физическими ядрами 2 и 3
qm set 101 --affinity 2-3
# несмежные ядра тоже можно перечислить через запятую
qm set 101 --affinity 2,3,8,9
В сыром libvirt то же самое делается тоньше — с привязкой каждого vCPU к своему физическому ядру по отдельности, что важно, когда у VM несколько vCPU и вы хотите держать их в пределах одного NUMA-узла:
<cputune>
<vcpupin vcpu='0' cpuset='2'/>
<vcpupin vcpu='1' cpuset='3'/>
</cputune>
Перед тем как расставлять привязку, обязательно посмотрите топологию NUMA на хосте — иначе есть риск закрепить vCPU на ядрах, которые физически принадлежат разным узлам, и получить обратный эффект вместо ожидаемой стабильности (что такое NUMA и почему это вообще важно для CPU, подробно разобрано в статье про NUMA и два CPU на сервере):
numactl --hardware
Практический ориентир: закрепляйте не отдельные vCPU по одному, а всю VM за ядрами одного физического сокета/NUMA-узла целиком — это даёт основной эффект от pinning (стабильность доступа к памяти) без микроменеджмента, который легко сломать при следующем изменении конфигурации VM.
Как выбрать настройки под свой сценарий
Единого правильного ответа нет — есть два полюса, между которыми вы выбираете позицию исходя из того, что для вас важнее прямо сейчас.
| Сценарий | Тип CPU | Флаги | CPU pinning |
|---|---|---|---|
| Одиночный сервер, миграций не планируется, важна максимальная скорость | host (host passthrough) | не нужны отдельно — с host все физические флаги уже доступны | стоит настроить для критичных нагрузок (БД, VPN-шлюз, low-latency сервисы) |
| Кластер с частой живой миграцией VM между разными узлами | эмулируемый профиль, общий для всех узлов (x86-64-v2/конкретное поколение по самому старому CPU в парке) | точечно добавляйте нужные (+aes и т.п.), если они одинаково поддерживаются на всех узлах кластера | обычно избегайте — pinning усложняет живую миграцию и балансировку, эффект от него на мигрирующей VM менее предсказуем |
| Смешанный парк: часть VM живёт на одном хосте всегда, часть мигрирует | настройка per-VM: host passthrough для «оседлых», эмулируемый тип для мигрирующих | по потребности конкретной VM | только для «оседлых» VM с чувствительной к задержкам нагрузкой |
Если вы не уверены, в какую сторону сместится ваш сценарий через полгода — начните с эмулируемого типа CPU и добавляйте флаги точечно по мере необходимости. Перейти с эмулируемого типа на host passthrough на одиночном сервере — вопрос одной команды и перезапуска VM. Обратный путь (снять зависимость от host passthrough, когда приложение уже негласно завязалось на конкретные инструкции) обычно требует более внимательной проверки и иногда — переустановки части системных компонентов внутри гостя.
Отдельно стоит сказать про диагностику: если после смены типа CPU или включения флагов производительность не выросла ожидаемо — не спешите винить настройку. Проверьте, не упираетесь ли вы в другое узкое место (диск, сеть, память), и посмотрите на steal time внутри гостя — он покажет, не забирает ли ресурсы CPU сосед по хосту, что часто маскируется под «неправильно настроенный CPU», хотя причина в overcommit планировщика.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Можно ли одновременно использовать host passthrough и CPU pinning?
Да, это не противоречащие друг другу настройки — host passthrough про то, что видит виртуалка, pinning про то, где физически исполняются её ядра. Часто их и комбинируют для нагрузок с максимальными требованиями к производительности на одиночном сервере.
Что будет, если попробовать мигрировать VM с host passthrough на хост с другим CPU?
Зависит от конкретной несовместимости: в лучшем случае гипервизор откажет в старте миграции с понятной ошибкой о несовместимости CPU-флагов, в худшем — VM запустится, но упадёт или начнёт вести себя нестабильно при обращении к отсутствующей на новом хосте инструкции. Полагаться на «повезёт» не стоит — заранее сверяйте flags в /proc/cpuinfo обоих хостов.
Нужно ли включать все возможные флаги CPU «на всякий случай»?
Нет — лишние флаги не дают выигрыша сами по себе (гость просто не пользуется тем, что не нужно приложению) и при этом сужают круг хостов, на которые VM можно потом безопасно перенести. Включайте то, что реально использует ваша нагрузка.
Как узнать, какие флаги нужны конкретно моему приложению?
Проверьте документацию приложения на предмет упоминания аппаратного ускорения (AES-NI, AVX и т.п.) или профилируйте нагрузку — если CPU явно узкое место на криптографии или векторных операциях, это прямой сигнал проверить соответствующие флаги.
CPU pinning поможет, если у меня всего одна VM на сервере?
Эффект будет заметно меньше, чем на плотно населённом хосте с несколькими VM, которые конкурируют за ядра — если VM одна, планировщику особо нечего балансировать. Pinning здесь имеет смысл в основном ради контроля над NUMA-топологией на многосокетных серверах.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →