CPU: измерить код и убрать лишнюю работу
Отделить стоимость функции от ожидания, не перепутать C# с нативным профилированием и проверить результат.
Выбрать правильный инструмент
В текущих исходниках 1.0.0 методы CSharpLanguage для запуска профилирования пусты, а получение статистики функций возвращает 0. Поэтому встроенная запись скриптового профилировщика не является подтверждённым способом увидеть время каждого C#-метода. Графики движка и собственные счётчики при этом имеют отдельные источники данных.
| Нужно узнать | Чем измерять | Ограничение |
|---|---|---|
| Длительность конкретного синхронного C# участка | System.Diagnostics.Stopwatch до и после участка | Реальное прошедшее время включает вытеснение потока и ожидания |
| Интервалы кадров и редкие длинные кадры | FrameTimingProbe из предыдущего руководства | Не показывает стек причины |
| Нативные вызовы, C# стеки, ожидания, GC | Профилировщик, поддерживающий нужный процесс, runtime и символы сборки | Сначала проверьте поддержку своего инструмента; пошаговая интеграция здесь не подтверждена |
| Число объектов или время завершённой операции | Собственный монитор | Callback вызывается при опросе, а не автоматически для каждого события |
Performance.GetMonitor(TimeProcess) и TimePhysicsProcess возвращают секунды. В этой сборке основной цикл примерно раз в секунду публикует накопленный максимум соответствующего времени и начинает новый интервал накопления. Это не текущее значение одного _Process, не процент CPU и не сумма времени всех C#-методов. Умножайте на 1000 только для показа миллисекунд.
Сократить измеренную работу
- Зафиксируйте вход, количество операций и правильный результат. Прогрейте C#-код, если сравниваете обычное выполнение после старта.
- Обведите секундомером только нужную синхронную операцию. Запись лога и форматирование результата делайте после замера. Повторите одинаковую серию.
- Проверьте алгоритм: полный перебор всех пар растёт иначе, чем поиск по индексу. Кэшируйте результат только с понятным правилом инвалидирования и пределом памяти.
- Уберите повторный поиск одной Entity/ресурса в каждом кадре, если ссылка уже известна и её жизненный цикл контролируется. Не заменяйте это кэшем навсегда на объект, который может быть удалён.
- Измерьте временные коллекции, строки и другие выделения отдельно. Частые сборки мусора могут давать задержки, но число выделенных байтов само по себе не равно времени кадра. Повторное использование массива требует владельца и корректного сброса.
- Снова сравните кадры и игровой результат в той же сборке и на том же устройстве.
Суммарное время функции с её дочерними вызовами и собственное время без них — разные показатели. Не складывайте родительскую длительность с длительностями всех её детей: получится повторный учёт. Ожидание другого потока или блокировки тоже не доказывает, что текущий поток выполнял полезную CPU-работу.
Когда переносить вычисление в поток
Перенос полезен, если работа достаточно крупная, независима от активного дерева и может завершиться позже. Полный пример считает простые числа в .NET-задаче и в группе WorkerThreadPool. Известный результат для 100000 — 9592; сравнивается корректность, а не обещанное ускорение.
Пример публикует время от постановки задачи до обработки результата в главном потоке. В него входят очередь, вычисление и ожидание следующего _Process. Не называйте это чистым временем функции. Разбиение короткой операции на множество задач может добавить больше расходов, чем сэкономить.
Если только 20% исходного времени поддаётся ускорению, даже мгновенное выполнение этой части оставит 80%: предел общего ускорения — 1 / 0,8 = 1,25. Сначала подтвердите долю работы. Дополнительные ядра не исправляют бесконечные ожидания, лишнюю загрузку и неверный алгоритм автоматически.