Engine & platform

Улучшение производительности поиска Burst Inspector

JONAS REHOLT / UNITY TECHNOLOGIESStudent Software Developer
Jul 11, 2023|7 Мин
Улучшение производительности поиска Burst Inspector
Эта веб-страница была переведена с помощью машинного перевода для вашего удобства. Мы не можем гарантировать точность или надежность переведенного контента. Если у вас есть вопросы о точности переведенного контента, обращайтесь к официальной английской версии веб-страницы.

Меня зовут Йонас Рехольт, и я студент, работающий в команде Burst. Я пишу в блоге, чтобы поделиться своим опытом оптимизации, который помог сделать возможными недавние изменения в производительности Burst Inspector. Поиск в Burst Inspector теперь работает в 13 раз быстрее, позволяя разработчикам быстрее сосредоточиться на коде, который им важен при оптимизации проектов.

Продолжайте читать, чтобы узнать, как Вы можете использовать Unity Profiler для исследования узких мест в производительности Вашей программы и как их устранить.

Знакомство с Burst Inspector

Компилятор Unity Burst преобразует Ваш код на C# в высокооптимизированный код на ассемблере. Burst Inspector позволяет Вам проверять код сборки прямо в редакторе Unity, поэтому Вам не нужно использовать внешние инструменты для простой проверки кода.

Когда Вы впервые откроете Burst Inspector и выберете целевое задание для отображения, Вы увидите окно, подобное изображенному ниже.

В окне Burst Inspector показан скомпилированный код сборки целевого задания Burst.
В окне Burst Inspector показан скомпилированный код сборки целевого задания Burst.

Как Вы можете видеть, Burst Inspector обеспечивает подсветку синтаксиса, стрелки ветвей и многое другое.

Инспектор попытается прокрутить страницу до сборки, которая реализует выбранную целевую функцию, но также полезно поискать в представлении сборки конкретные инструкции, комментарии и т.д. Это подводит нас к теме этой статьи в блоге.

Повышение производительности текстового поиска

Чтобы выполнить поиск, инспектор должен найти исходный вывод сборки и преобразовать эти индексы в позиции в представлении инспектора. Оригинальная функциональность поиска следовала схеме, показанной ниже, и в значительной степени опиралась на реализацию System.String.IndexOf(*).

while (assemblyCode.IndexOf(key, accIdx) >= 0) {
// ...	
// Do logic for handling search hits
// ...
}

Выполнение вышеуказанного поиска на 135 582 строках ассемблерного кода для общего поискового хита (всего 21 769 хитов) привело к времени выполнения около 12 секунд для первого поиска и около 5 секунд для последующих поисков. Это не совсем желательное время ожидания для события GUI, поэтому мы должны были что-то предпринять. Выполнение поиска с помощью Unity Profiler показало, что 37,3% времени выполнения было потрачено на IndexOf(*), как показано ниже.

Профилированный запуск поиска обычной строки в Burst Inspector
Профилированный запуск поиска обычной строки в Burst Inspector

Разумная оптимизация должна устранить зависимость от этой функции, либо сделав собственную реализацию, либо полностью изменив алгоритм. Независимо от того, какой алгоритм будет использован, он будет включать в себя перебор всей строки. Таким образом, для поиска совпадений требуется некоторая собственная реализация. Учитывая это, представляется целесообразным начать оптимизацию с сохранения оригинального алгоритма, но создания собственной функции IndexOf.

3,34 секунды, потраченные на LongTextArea.GetFragNrFromBlockIdx(), связаны с получением неокрашенного кода сборки. Это используется для выполнения поиска. В настоящее время Burst Inspector сохраняет ассемблерный код дважды - один раз в формате для рендеринга, а второй раз в неформатированном виде.

Написание пользовательской функции также имеет приятный побочный эффект - сокращение количества вызовов, поскольку в настоящее время вызов выполняется для каждого поискового запроса плюс один.

Исходный код IndexOf(*) показывает множество проверок безопасности, необходимых для надежной общей реализации. Однако в нашем случае мы можем с уверенностью предположить, что большинство этих проверок верны. Чтобы попытаться выжать все до последней капли производительности, Вы захотите создать C-подобную функцию, чтобы избежать таких вещей, как проверка границ.

Вы можете написать функцию, следуя приведенному ниже псевдокоду, где IsKeyMatch(*) просто проверяет, совпадает ключ или нет.

List<int> Search(string assemblyCode, string key, int accIdx) {
     var hits = new List<int>();
     for (i = accIdx; i < assemblyCode.len - key.len; i++) {
	     if (IsKeyMatch(assemblyCode, key, i)) {
		     hits.add(i);
	     	     i += key.len-1;
          }
     }
     return hits;
}

Однако, поскольку C# - управляемый язык, эта C-подобная функция требует, чтобы Вы прикрепили используемые управляемые объекты, чтобы сборщик мусора не перемещал адреса памяти. Вот шаблонный код:

unsafe {
	fixed (char* source = assemblyCode) {
		fixed (char* needle = key) {
			CustomIndexOf(source, key)
		}
	}
}

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

matches = FindAllMathces(text, key)
foreach match {
	...
	Do logic for handling search hits
	...
}

Каковы были достижения? Используя небольшой пример из предыдущей статьи, это изменение в коде дает 6,6-кратное ускорение при первом вызове и 13,2-кратное ускорение при последующих вызовах (измеряется как старый/новый). Меньшее ускорение при начальном поиске связано с затратами на загрузку неформатированной сборки, чтобы избежать поиска совпадений в цветовых строках.

Измерения времени выполнения поиска текста в Burst Inspector
Измерения времени выполнения поиска текста в Burst Inspector

Благодаря этим улучшениям поиск с большой нагрузкой, содержащий чуть менее 22 000 совпадений, теперь будет занимать около 1,8 секунды для первоначального поиска и около 0,4 секунды для последующих поисков. Это делает Burst Inspector более удобным для работы с большими сборками, так как теперь не нужно тратить время на приготовление чашки чая во время каждого поиска.

Вы можете воспользоваться этим улучшением производительности уже сейчас с пакетом Burst 1.8.7.

Хотите узнать больше о Burst? Общайтесь с нами на форуме Burst. Обязательно следите за новыми техническими блогами от других разработчиков Unity в рамках продолжающейсясерииTech from the Trenches.