Комментарии 23
А можно подменять свойство, что приведет к автоматическому ускорению с использованием старого кода, например, так. В примере получается х2 ускорение + еще х2 ускорение (в результате получается х4 от исходного) если использовать кешированную переменную в классах-наследниках, потому что дергание свойства — это по сути вызов метода с вложенными проверками, которые можно убрать.
Да, это вполне вариант для старого кода. Его производительность тоже рассмотрена в статье (вариант свойства с флагом).
Такой вопрос — а зачем вам дополнительная проверка _cachedTransform == null в Awake()?
И спасибо за участие в обсуждении)
Такой вопрос — а зачем вам дополнительная проверка _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. Примеры в описании ошибочны — нельзя трогать rigidbody в Update, только в FixedUpdate, не нужно учить плохому.
Что означает 0% в WebGL на графике «Сравнение производительности»?

Судя по графикам, получение свойства через
Судя по графикам, получение свойства через
GetComponent<T>()
в Unity5 настолько быcтрый, что значительно быстрее получения из словаря. Может, разница настолько мала, что нету смысла стараться писать всякие ужасные конструкции и остановится на лаконичности и надежности кода и оптимизировать другие вещи? Тем более, что ни один из более быстрых способов не проверяет компонент на существование.Для WebGL цифры из профайлера довольно странные, на 1000 вызов тестовых методов c 0% занимает 0.00 ms, а если увеличивать количество вызовов, то это приводит к рандомным вызовам сборщика мусора, который ломает статистику. Так что сложно сказать, как это адекватно померить.
Да, так и есть, быстрее GetComponent только изначальное сохранение компонентов при инициализации или вообще не в рантайме, последние варианты именно об этом.
Конечно, тут нужно смотреть на конкретику и использовать профайлер — кэшировать данные обычно имеет смысл для объектов, которые часто создаются либо используются или их количество реально большое.
Да, так и есть, быстрее GetComponent только изначальное сохранение компонентов при инициализации или вообще не в рантайме, последние варианты именно об этом.
Конечно, тут нужно смотреть на конкретику и использовать профайлер — кэшировать данные обычно имеет смысл для объектов, которые часто создаются либо используются или их количество реально большое.
Для WebGL цифры из профайлера довольно странные, на 1000 вызов тестовых методов c 0% занимает 0.00 ms,
Может, вы замеряете каждый отдельный метод, а потом считаете их сумму? Что-то вроде:
loop {
start = now();
GetComponent(Class);
sum = now() - start();
}
Если использовать не
perfomance.now()
, а Date.now()
, то все, что меньше миллисекунды в целом будет считаться как 0.И еще — в случае перекрёстных ссылок, получается, что где-то будет использоваться редактор, где-то аттрибут, где-то аксессор. Я могу ошибаться, но меня и моих коллег, вероятно, раздражало бы различная инициализация ссылок на одинаковые по смыслу объекты
Спасибо за статью! Правда хочу заметить что, Unity вроде как уже не имеет приставки 3D. Хотя трудно сказать, на сайте приписки 3д нет но есть в адресе. Интересно как юридически называется.
На самом деле не трудно www.wipo.int/branddb/en/showData.jsp?ID=USTM.86128413
В каждом новом проекте приходится вычищать такие вот кеши, которые увеличивают сложность и ухудшают читаемость ради преждевременной оптимизации. А варианты с отдельным классом под кеши это еще и дополнительная связанность, даже в самой юньке наконец то догадались об этом и все кеши сделали обсолет.
Если бы была хотя бы попытка потестировать эти «кеши», то было бы понятно, что кешированием там и не пахло — по скорости оно было соизмеримо с постоянным GetComponent(typeof(T)). Юнитеки просто почистили апи от бесполезных свойств. Кеширование они прикрутили только к transform, но все-равно — ручное кеширование в несколько раз быстрее текущего «кешированного» варианта.
>>Например, в вашей игре есть пулемет, который стреляет пулями, каждая из которых является отдельным объектом (что само по себе неправильно, но это же сферический пример)
А почему неправильно? Я всегда использую абстрактный класс-скрипт с функцией SetDamage, от которого наследую и определяю SetDamage для каждого объекта, который может получить урон (соответственно, каждый объект и получает такой скрипт-потомок). Пуля же, сталкиваясь с объектом, просто вызывает эту функцию и урон наносится. Способ сам придумал, хотелось бы понять, почему так лучше не делать. Пули в пуле, если что, непонятно, почему им объектами быть не следует.
А почему неправильно? Я всегда использую абстрактный класс-скрипт с функцией 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
);
//. . .
}
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Особенности кэширования компонентов в Unity3D