сеньорчикОткрыть в Telegram
← вся теориятеория к собесу · unsafe и производительность Rust

Бенчмарки и профилирование в Rust

Бенчмарки и профилирование

Завершающий блок: как измерять. Спрашивают, почему бенчмарк показал ноль, зачем criterion и чем бенчмарк отличается от профилирования.

Стержень: бенчмарк отвечает «стало ли быстрее», профилировщик - «где именно тратится время»; путать их бессмысленно.

// Формулировки: «почему замер дал ноль наносекунд?», «зачем criterion?», «как искать горячие места?»

Почему замеры врут

Первая причина - debug-сборка: без инлайнинга и оптимизаций, зато с проверками переполнения, меняется не только время, но и профиль. Горячим окажется совсем не то место, что в проде.

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

// Третья - шум: холодный кэш, частота процессора, соседние процессы. Один прогон через Instant::now ничего не доказывает.

black_box
барьер, скрывающий значение от оптимизатора
свёртка констант
вычисление выражения на этапе компиляции

Инструменты под задачу

criterion берёт на себя всё перечисленное: прогрев, множество итераций, статистику по распределению и сравнение с предыдущим прогоном - он прямо говорит, изменение значимо или это шум. Это ответ на вопрос «стало ли быстрее».

Вопрос «где тратится время» решает семплирующий профилировщик: он снимает стеки вызовов много раз в секунду, а флеймграф собирает их в картину, где ширина полосы - доля времени. Ручные таймеры годятся только для проверки готовой гипотезы, а сами гипотезы обычно неверны.

// Отдельный сюжет - хвост задержек. Разрыв между медианой и p99 делают не вычисления, а ожидания: очередь за замком, нехватка воркеров, всплеск аллокаций, блокирующий вызов в асинхронной задаче. Ищут это трассировкой по стадиям, а не профилем CPU.

criterion
бенчмарки со статистикой и сравнением прогонов
флеймграф
картина стеков по доле занятого времени

Как отвечать: «Бенчмарк показал ноль наносекунд. Что произошло?»

Оптимизатор выбросил вычисление. Если результат нигде не используется, а вход - константа, всё сворачивается ещё при компиляции, и мерить оказывается нечего. Лечится black_box вокруг входных данных и результата: он прячет значения от оптимизатора и заставляет код реально выполняться. Именно поэтому я беру criterion, а не пишу замер руками: там уже есть и прогрев, и множество итераций, и статистика, которая отличает реальное изменение от шума. И меряю всегда на release, потому что debug даёт не только другие числа, но и другой профиль.

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

На чём валятся

  • Меряют на debug-сборке.
  • Забывают black_box и получают нулевое время «идеальной» функции.
  • Делают один замер через Instant::now и делают выводы по шуму.
  • Ищут горячие места догадками вместо профилировщика.
  • Оптимизируют среднее, когда пользователи жалуются на хвост.

Проверьте себя

Пять вопросов из банка по этой подтеме. Всего их 11, остальные разбираются в тренажёре.

  1. #rs_bench_profile1 / 5
    Что даёт criterion по сравнению с ручным замером через Instant::now()?
    A)Прогрев, много итераций и статистику по изменениям
    B)Возможность запускать бенчмарки на стабильном компиляторе
    C)Профиль по строкам кода с указанием горячих мест
    D)Замер потребления памяти вместе со временем выполнения
    показать ответ и разбор
    +A)Прогрев, много итераций и статистику по изменениям

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

  2. #rs_bench_profile2 / 5
    Сервис держит нагрузку, но p99 в пять раз хуже медианы. Куда смотреть?
    A)На алгоритмическую сложность горячей функции
    B)На среднюю загрузку процессора: при высокой она растягивает все запросы
    C)На размер бинарника: он влияет на промахи кэша инструкций
    D)На блокировки, ожидание в пуле и всплески аллокаций
    показать ответ и разбор
    +D)На блокировки, ожидание в пуле и всплески аллокаций

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

  3. #rs_bench_profile3 / 5
    Как обычно ищут горячие места в Rust-сервисе под нагрузкой?
    A)Семплирующим профилировщиком и флеймграфом по стекам
    B)Прогоном юнит-тестов с включённым таймером
    C)Печатью времени вокруг подозрительных функций
    D)Сравнением размеров бинарника до и после изменений
    показать ответ и разбор
    +A)Семплирующим профилировщиком и флеймграфом по стекам

    // разбор: Семплирование снимает стеки много раз в секунду и показывает, где программа реально проводит время; флеймграф собирает их в картину, где ширина полосы — доля времени. Ручные таймеры годятся для проверки гипотезы, но сами гипотезы обычно неверны — узкое место почти всегда не там, где кажется.

  4. #rs_bench_profile4 / 5
    Что делает black_box в бенчмарке?
    A)Замеряет время выполнения переданного выражения с точностью до наносекунд
    B)Запускает выражение в отдельном потоке, изолируя его от остального кода
    C)Скрывает значение от оптимизатора, чтобы вычисление не выбросили как ненужное
    D)Прогревает кэш процессора перед основной серией измерений в бенчмарке
    показать ответ и разбор
    +C)Скрывает значение от оптимизатора, чтобы вычисление не выбросили как ненужное

    // разбор: Оптимизатор вправе выбросить вычисление, результат которого никому не нужен, — и микробенчмарк начинает показывать ноль наносекунд. black_box делает значение непрозрачным: компилятор перестаёт рассуждать о нём и вынужден выполнить работу. Оборачивают и вход, чтобы константы не свернулись при компиляции, и результат, чтобы вычисление не удалили.

  5. #rs_bench_profile5 / 5
    Бенчмарк показывает разброс в 30% между прогонами. Что делать до того, как верить числам?
    A)Взять минимальное из измерений: оно ближе всего к чистому времени выполнения
    B)Сравнивать только среднее арифметическое: оно устойчиво к случайным выбросам
    C)Отключить оптимизации, чтобы убрать влияние компилятора на разброс результатов
    D)Увеличить число итераций и смотреть на распределение, а не на одно число
    показать ответ и разбор
    +D)Увеличить число итераций и смотреть на распределение, а не на одно число

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

дальше

Теорию прочитали. Навык ставится повторением

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