C++ профилирование: от медленного действия к причине
CPU/GPU, symbols, правильный процесс, sampling/tracing и сравнение результатов.
Начинайте с конкретного медленного действия: кадр в определённой локации, импорт ресурса или загрузка уровня. C++ profiler нужен, когда требуется увидеть native работу движка/расширений. Для C# логики дополнительно нужен managed profiler; native стек сам по себе не раскрывает всю работу managed кода.
CPU, GPU или ожидание
| Наблюдение | Что исследовать |
|---|---|
| Поток занят вычислениями | CPU samples и стеки, затем дорогие функции и вызывающий код |
| Поток ждёт | Timeline, синхронизация, I/O и ожидание кадра; большой wall time не означает столько же CPU работы |
| Стоимость растёт с разрешением/эффектами | GPU timing и render capture отдельно от CPU samples |
| Медленно только при первом открытии | Импорт, загрузка и прогрев; отделите их от стабильной игры |
| Редактор медленный, самостоятельная игра нет | Снимайте именно editor workload, а не соседний игровой процесс |
Один показатель FPS не показывает причину. Сохраните разрешение, renderer, VSync/лимит частоты и положение камеры вместе с воспроизводимым действием. Headless-запуск не подтверждает стоимость GPU и окна редактора.
Sampling и tracing
Sampling периодически собирает стеки и помогает найти, где тратится CPU время. Результат статистический: короткий захват и редкая функция могут дать мало samples. Tracing показывает интервалы явно размеченных событий; отсутствие зоны не означает отсутствие работы. Native CPU profiler не становится GPU profiler от фильтра по render thread.
Для Windows доступны отдельные руководства Very Sleepy и Superluminal. Для других систем см. Hotspot и Instruments. Выбирайте инструмент под нужный тип данных и платформу, а не ожидайте одинаковый workflow от всех приложений.
Бинарник и символы
Захват адресов может работать без debug symbols. Чтобы видеть имена функций/строки исходников, нужны символы, соответствующие именно проверяемым executable и native библиотекам. Совпадения «версия 1.0.0» недостаточно: храните hash бинарника, символы и source revision одной сборки. Наличие C# PDB не заменяет native symbols.
При подготовке отдельной сборки debug_symbols=yes включает символы, не отключая автоматически оптимизацию. production=yes debug_symbols=yes сохраняет явный запрос символов, но production также меняет другие defaults, включая LTO. Сравнивайте одинаковые compile/link настройки. optimize=speed_trace меняет оптимизацию относительно speed; результат такой сборки нельзя без проверки переносить на release.
В текущей MSVC ветке символы включают /Zi и /DEBUG:FULL; в других toolchain используются DWARF flags. Храните отдельные символы, если сборка их отделяет, и настройте поиск символов в profiler. Одинаковое имя PDB или исходного файла не подтверждает соответствие бинарнику. Сначала добейтесь читаемых стеков своего модуля, затем интерпретируйте строки кода.
Выберите правильный процесс
| Цель | Режим запуска |
|---|---|
| Самостоятельный проект | Исполняемый файл движка с --path к папке, содержащей project.reckfell |
| Редактор проекта | Дополнительно --editor |
| Открытие/закрытие минимального процесса | --quit после выбранного режима; это только первая итерация |
Рабочая папка профилировщика не является надёжной заменой явного --path. Project Manager, editor и Play могут быть разными PID. Если профиль содержит только выбор проекта или idle, проверьте путь executable, аргументы и PID целевого процесса. Для захвата будущих дочерних процессов нужна соответствующая функция profiler, а не предположение, что attach следует за ними автоматически.
--quit выходит после первой итерации main loop. Он не означает «уровень полностью загрузился», завершение deferred/async задач или воспроизводимый steady-state кадр. Для короткого старта используйте запуск приложения самим profiler; attach может не успеть. Для игрового сценария уберите --quit, дождитесь нужного состояния и снимайте один и тот же участок.
Разберите и повторите
- Сначала подтвердите правильный процесс, разрешённые symbols и отсутствие ошибок загрузки проекта.
- Для steady-state выполните одинаковый прогрев и захватите повторяемое действие несколько раз. Холодный старт измеряйте отдельно, не прогревая его случайно.
- Различайте inclusive время функции с вызванными функциями и exclusive время её собственного кода. Не суммируйте inclusive строки как независимые затраты кадра.
- Сначала смотрите весь процесс и потоки, затем сужайте модуль/интервал. Скрытие driver/system frames не убирает их реальную стоимость.
- Исправляйте конкретную причину, повторяйте тот же сценарий и сравнивайте абсолютное время при одинаковых условиях. Доля функции может уменьшиться из-за роста другой работы; одного процента недостаточно.
Microbenchmark полезен после нахождения hotspot: измеряйте конкретную операцию с используемым результатом и одинаковыми входами. Пустая петля или запись в неиспользуемую переменную могут быть оптимизированы целиком. static сам по себе не защищает от удаления работы. Microbenchmark не заменяет проверку полного сценария и не требует добавлять пустые тесты в исходники движка.
Инструментированные зоны
В исходниках есть profiler=tracy, profiler=perfetto, profiler=instruments; default — none. При отсутствии backend profile macros являются заглушками. Настройка приложения проекта не добавляет эти зоны в уже собранный бинарник. Нужна отдельная сборка с соответствующим backend; её создание здесь не выполнялось.
См. Tracy, Perfetto и Instruments. Для Tracy/Perfetto build scripts проверяют путь к SDK; поддержка callstack/memory options различается. Наличие исходного вызова zone macro не подтверждает записанный trace. Публично доступные готовые instrumented Reckfell builds здесь не подтверждены.
Границы проверки
Параметры сборки, symbols и tracing gates сверены с исходниками Reckfell 1.0.0. На установленном Runtime выполнен отдельный headless CLI-тест: --path запускает игровой target, --quit завершает его после первой итерации. Это не CPU/GPU измерение. Захват сторонним profiler, symbol resolving, оптимизированная сборка и доказательство ускорения не выполнялись. Движок, Runtime и SDK не менялись.