PGO в Ubuntu: case study по QEMU и сборкам RISC-V

В блоге Ubuntu Server вышел разбор Profile-Guided Optimization (PGO) на реальном стеке: эмуляция RISC-V в QEMU, на которой крутятся сборки пакетов в Launchpad. Не абстрактный «ускорим всё», а замеры на четырёх пакетах, с цифрами по CPU и времени сборки. Ниже – что такое PGO, как измеряли и какие выводы сделали.

Что такое PGO

Profile-Guided Optimization (ещё называют FDO, feedback-driven optimization) – схема из двух (и больше) проходов компиляции. Сначала бинарник собирают так, чтобы снять профиль реального выполнения. Потом компилятор (в кейсе Ubuntu – GCC) использует этот профиль и сильнее оптимизирует «горячие» ветки: раскладку кода, инлайнинг, предсказание ветвлений.

Классическая инструментация внутри бинарника тормозит прод, поэтому в исследовании опирались на внешний sampling через perf (PMU ядра Linux) и конвертацию данных в формат, понятный GCC, через пакет autofdo. Так профиль ближе к боевой нагрузке, без жёсткого штрафа от полной instrumented-сборки.

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

Зачем смотрели именно QEMU и RISC-V

Ubuntu давно собирает пакеты под RISC-V. Когда «железа» не хватало, builders в Launchpad подняли как QEMU-эмуляцию RISC-V поверх AMD64. Эти эмулированные builders до сих пор делают сотни сборок в день. Для Server team это идеальный кандидат: тяжёлая, повторяемая, узкоспециализированная нагрузка – как раз то, где PGO обычно даёт максимум.

Как собирали профиль и нагрузку

Профиль снимали с QEMU во время сборки пакетов внутри гостевой RISC-V среды. GCC сам по себе не ест raw-файлы perf, поэтому цепочка: perf record → конвертация через autofdogcov-подобный вход для повторной сборки QEMU с PGO.

Критерии пакетов для нагрузки:

  • сборка на RISC-V builders разумной длины (ориентир: больше часа, но не «на весь день»);
  • разный стек языков/фреймворков, чтобы проверить перенос оптимизации с одной нагрузки на соседние.

Итоговый набор:

  • OpenSSL (C) – по нему снимали профиль и собирали PGO-QEMU;
  • GDB (C++);
  • Emacs (C + много Elisp);
  • Python 3.12 (C + много Python-кода в сборке).

Сначала мерили «обычный» QEMU на всех четырёх, затем – QEMU, собранный с PGO по профилю OpenSSL. Для статистики – средние по 5 прогонам на пакет.

Железо и параметры съёмки

Тестовый стенд из статьи: AMD Ryzen 9 7950X3D (16 ядер), 128 GB DDR5 ECC, SSD Gen4. QEMU для RISC-V virt с 8 vCPU и 8 GB RAM, без дисплея, с OpenSBI/U-Boot. perf писали с ветвлениями (branch_insn / branch-any), sampling пачками: 5 минут записи, пауза, снова запись – из-за размера perfdata (иначе легко вылезти за десятки гигабайт).

perf record \
  -e branch_insn \
  --branch-any \
  -p ${QEMU_PID} \
  --all-cpus \
  --count 100003 \
  --quiet \
  --output output.perfdata \
  -- sleep 300

Результаты

Смотрели среднее использование CPU (в минутах) и среднее время сборки. Везде PGO-QEMU выигрывал примерно в одном коридоре 5-7%.

  • OpenSSL: CPU примерно -5.3%, время сборки около -5.5% (порядка 2 минут). Ожидаемо: профиль как раз с этой нагрузки.
  • GDB: CPU около -5.5%, время около -5.7% (порядка 4 минут). C++-сборка тяжелее линковкой, но выигрыш удержался.
  • Emacs: один из лучших результатов – CPU около -5.7%, время около -6.7% (порядка 5.5 минут).
  • Python 3.12: CPU около -5.2%, время около -6.3% (порядка 7.5 минут).

Интересная деталь по OpenSSL: процент загрузки CPU в моменте мог быть чуть выше, но из-за более короткой сборки суммарные CPU-минуты всё равно падали. На масштабе Launchpad (много сборок в сутки) даже 5% времени – это часы машинного времени и потенциально больше параллельных задач.

Что из этого следует

PGO здесь сработал: и CPU, и wall-clock улучшились на похожих, но не идентичных нагрузках (разные языки, один класс работы – сборка пакета в эмуляторе). Это хороший аргумент для узких, повторяемых сценариев: CI builders, эмуляция, сервисы с предсказуемым hot path.

При этом авторы прямо пишут: PGO не серебряная пуля. Чем шире и разнообразнее workload, тем слабее эффект; на «чужой» нагрузке профиль может даже навредить. Если код огромный и руками через perf top / perf record сложно найти рычаг, PGO – разумный автоматический слой. Если нагрузка пляшет каждый день – сначала стоит проверить, есть ли вообще стабильный профиль.

Для пользователей дистрибутива

Этот пост Canonical – case study по QEMU для builders, а не анонс «PGO включили во все пакеты Ubuntu». Пользователь desktop/server не обязан ничего пересобирать: смысл материала в методологии и в том, куда имеет смысл вкладывать инженерное время. Для тех, кто сам гоняет тяжёлую эмуляцию или кастомные пакеты, цепочка perf + AutoFDO + GCC PGO описана и в документации Ubuntu Server.

Краткий итог

Ubuntu Server team показала на практике: PGO на QEMU (профиль с OpenSSL, проверка на GDB/Emacs/Python) даёт устойчивые ~5-7% по CPU и времени сборки в эмуляции RISC-V. Техника сильна на специализированных нагрузках и требует честного профилирования, а не «включи флаг и забудь». Для дистрибутива и CI это реальный рычаг; как единственную стратегию оптимизации всего подряд её рассматривать не стоит.

Источники и ссылки


Комментарии загружаются…