Как стать автором
Обновить

Комментарии 23

А можно подменять свойство, что приведет к автоматическому ускорению с использованием старого кода, например, так. В примере получается х2 ускорение + еще х2 ускорение (в результате получается х4 от исходного) если использовать кешированную переменную в классах-наследниках, потому что дергание свойства — это по сути вызов метода с вложенными проверками, которые можно убрать.
Да, это вполне вариант для старого кода. Его производительность тоже рассмотрена в статье (вариант свойства с флагом).
Такой вопрос — а зачем вам дополнительная проверка _cachedTransform == null в Awake()?
И спасибо за участие в обсуждении)
Его производительность тоже рассмотрена в статье

Тут акцент был именно на слове «new» перед transform — мы переопределяем поведение этого свойства на свое — закешированное, нет необходимости переписывать старый код под CachedTransform, можно использовать «штатный» transform.

проверка _cachedTransform == null в Awake()

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

Те это что-то типа гарантии что трансформ закеширован на старте и дальше во всех методах этого класса и классов-наследников можно напрямую щупать _cachedTransform — будет еще двойное ускорение по сравнению с закешированным «штатным» transform.
Но тут есть тонкость — если компонент повесить на GO и выключить GO в редакторе до старта — awake не отработает пока GO не будет активирован. Те компонент вроде как и существует, но Awake не пройден. Те можно получить ситуацию с неопределенным _cachedTransform, если повесить компонент на выключенный GO + добавить его через инспектор в другой компонент на включенном GO и подергать паблик-методы, связанные с кешированным трансформом. Тут уже нужно самому следить за логикой поведения, либо игнорировать _cachedTransform и работать только через transform.
Да, теперь понятно, спасибо.
Еще 2 замечания по поводу репы на гитхабе:
1. Не указана лицензия использования. Ее отсутствие автоматически запрещает использование кода.
2. Примеры в описании ошибочны — нельзя трогать rigidbody в Update, только в FixedUpdate, не нужно учить плохому.
1. Добавил лицензию
2. Да, вы правы. Примеры были из разряда «сделать что угодно», так что об этом успешно было забыто. Исправлено
Что означает 0% в WebGL на графике «Сравнение производительности»?
image

Судя по графикам, получение свойства через GetComponent<T>() в Unity5 настолько быcтрый, что значительно быстрее получения из словаря. Может, разница настолько мала, что нету смысла стараться писать всякие ужасные конструкции и остановится на лаконичности и надежности кода и оптимизировать другие вещи? Тем более, что ни один из более быстрых способов не проверяет компонент на существование.
Для WebGL цифры из профайлера довольно странные, на 1000 вызов тестовых методов c 0% занимает 0.00 ms, а если увеличивать количество вызовов, то это приводит к рандомным вызовам сборщика мусора, который ломает статистику. Так что сложно сказать, как это адекватно померить.
Да, так и есть, быстрее GetComponent только изначальное сохранение компонентов при инициализации или вообще не в рантайме, последние варианты именно об этом.
Конечно, тут нужно смотреть на конкретику и использовать профайлер — кэшировать данные обычно имеет смысл для объектов, которые часто создаются либо используются или их количество реально большое.
Для WebGL цифры из профайлера довольно странные, на 1000 вызов тестовых методов c 0% занимает 0.00 ms,

Может, вы замеряете каждый отдельный метод, а потом считаете их сумму? Что-то вроде:
loop {
  start = now();
  GetComponent(Class);
  sum = now() - start();
}


Если использовать не perfomance.now(), а Date.now(), то все, что меньше миллисекунды в целом будет считаться как 0.
Использовался встроенный профайлер, в котором замерялся кусок кода с N инсттрукций в цикле, так что видимо не в этом дело.
KonH у вас есть реальный проект, где вы применяете один из ваших методов кеширования (кроме того, что автоматический назначает переменные в редакторе) и вы получили необходимый прирост фпс или полной загрузки сцены?
Довольно широко использовали свойства, прирост производительности при большой частоте обращений наблюдался.
Спасибо!
И еще — в случае перекрёстных ссылок, получается, что где-то будет использоваться редактор, где-то аттрибут, где-то аксессор. Я могу ошибаться, но меня и моих коллег, вероятно, раздражало бы различная инициализация ссылок на одинаковые по смыслу объекты
Да, это одна из проблем данного подхода, верно замечено. К сожалению, универсальный вариант пока сложно найти.
Спасибо за статью! Правда хочу заметить что, Unity вроде как уже не имеет приставки 3D. Хотя трудно сказать, на сайте приписки 3д нет но есть в адресе. Интересно как юридически называется.
В каждом новом проекте приходится вычищать такие вот кеши, которые увеличивают сложность и ухудшают читаемость ради преждевременной оптимизации. А варианты с отдельным классом под кеши это еще и дополнительная связанность, даже в самой юньке наконец то догадались об этом и все кеши сделали обсолет.
Если бы была хотя бы попытка потестировать эти «кеши», то было бы понятно, что кешированием там и не пахло — по скорости оно было соизмеримо с постоянным GetComponent(typeof(T)). Юнитеки просто почистили апи от бесполезных свойств. Кеширование они прикрутили только к transform, но все-равно — ручное кеширование в несколько раз быстрее текущего «кешированного» варианта.
>>Например, в вашей игре есть пулемет, который стреляет пулями, каждая из которых является отдельным объектом (что само по себе неправильно, но это же сферический пример)

А почему неправильно? Я всегда использую абстрактный класс-скрипт с функцией SetDamage, от которого наследую и определяю SetDamage для каждого объекта, который может получить урон (соответственно, каждый объект и получает такой скрипт-потомок). Пуля же, сталкиваясь с объектом, просто вызывает эту функцию и урон наносится. Способ сам придумал, хотелось бы понять, почему так лучше не делать. Пули в пуле, если что, непонятно, почему им объектами быть не следует.
Если не требуется отображать сами пули (то есть пользователь их не видит в силу скорости или этим можно пренебречь), то достаточно системы партиклей и использование Raycast. Если их нужно видеть в 2д (и чуть сложнее в 3д) — то также может быть достаточно рейкастов и проверки их коллизий (но тут нужно сравнивать производительность, не берусь сходу утверждать).
В любом случае, если требуется создавать и уничтожать объекты много и часто — нужно как минимум использовать пул.
Спасибо за статью. Почитать о таком тщательном взгляде на проблему (даже спустя три года) было любопытно. Особенно полезным оказался раздел про пользовательские атрибуты. Давно собирался разобраться в том, как их реализовывать.

Интересно насколько (в затратах времени на спаун объектов) поменялось быстродействие вашего приложения после добавления оптимизаций, описанных в статье. И насколько этот подход использовался в приложении дальше.
Кстати, немного оффтоп — у себя в домашнем проекте использую самописный getComponent<>() на стероидах. Позволяет не только получить компонент, но создать его и/или проверить на наличие если он должен быть. Не смотря на некоторую громоздкость, семантически точно описывает ситуацию, впитывает в себя механические проверки и действия.

Код
class XUtils
{
    // . . .
	
    public enum AccessPolicy {
    	JustFind,
    	ShouldExist,
    	CreateIfNo,
    	ShouldBeCreated
    }
    
    public static T_Type getComponent<T_Type>(GameObject inGameObject,
    	AccessPolicy inComponentAccessPolicy = AccessPolicy.JustFind)
    	where T_Type : Component
    {
    	T_Type theComponent = inGameObject.GetComponent<T_Type>();
    	if (theComponent) {
    		check(AccessPolicy.ShouldBeCreated != inComponentAccessPolicy);
    	} else {
    		switch(inComponentAccessPolicy) {
    			case AccessPolicy.CreateIfNo:
    			case AccessPolicy.ShouldBeCreated:
    				theComponent = inGameObject.AddComponent<T_Type>();
    				break;
    
    			case AccessPolicy.ShouldExist:
    				check(false);
    				break;
    		}
    	}
    
    	return theComponent;
    }
    
    // . . .
}



Пример использования
private void Awake() {
    //. . .
    rigidBody = XUtils.getComponent<RigidBody2D>(
        this, XUtils.AccessPolicy.ShouldExist
    );
    //. . .
}


Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации

Истории