Unity3D - это внушительный набор средств для кроссплатформенной разработки игр и 3D-приложений. Однако ряд его особенностей может привести к внезапному падению производительности продукта на мобильных платформах.
Где же прячутся подводные камни? Как обеспечить оптимальный user experience на старом смартфоне? Каких "граблей" стоит избегать при написании кода и подготовке графики? Рассмотрим на примере RPG "Гильдия Героев" для Android и iOS.
7. Unity3D
PC & Console games Mobile games
Architectural visualizations
Medical simulations
Military simulations
CAD
Research
Interactive presentations
…
Education
Media & movies
Art installations
8. Unity3D
PC & Console games Mobile games
Architectural visualizations
Medical simulations
Military simulations
CAD
Research
Interactive presentations
…
Education
Media & movies
Art installations
22. Главный совет
“Make optimization a design consideration, not a final step”
- Unity Docs
Постоянно спрашивай себя: “А не ерунду ли я сейчас делаю?”
- Примерный перевод
33. Импорт ресурсов: анимации
• Сложные анимации занимают много памяти. KISS.
• Анимация большого числа объектов нагружает ЦП.
34. Импорт ресурсов: анимации
• Сложные анимации занимают много памяти. KISS.
• Анимация большого числа объектов нагружает ЦП.
• Можно уменьшить количество объектов.
35. Импорт ресурсов: анимации
• Сложные анимации занимают много памяти. KISS.
• Анимация большого числа объектов нагружает ЦП.
• Можно уменьшить количество объектов.
• Можно упростить объекты.
?!
41. • Не включайте Read/Write.
• Отключите mipmaps, если возможно.
• Не используйте огромные текстуры.
• 2048x2048 или 1024x1024 для UI
• 512x512 или меньше для текстур моделей
Импорт ресурсов: текстуры
*Допустимо, если есть запас производительности GPU
-50%
-33%*
49. Код и runtime
• Сборка мусора работает плохо.
• Heap удваивается при достижении лимита. И не уменьшается никогда.
• Производительность игры со временем снижается.
50. Код и runtime: Garbage collection
ReferenceType
class Entry {
uint id;
long phone;
string name;
}
Thread stack (FAST)
var e1 = new Entry();
var e2 = e1;
Managed heap (SLOW)
ref1
Entry
id: 1337
phone: 88005553535
name: ref2
String
value: “Ayy Lmao”
ref3
51. Код и runtime: Garbage collection
ValueType
struct Entry {
uint id;
long phone;
string name;
}
Thread stack (FAST)
var e1 = new Entry();
var e2 = e1;
Managed heap (SLOW)
Entry
id: 1337
phone: 88005553535
name: ref1
String
value: “Ayy Lmao”
Entry (сopy)
id: 740
phone: 88005553535
name: ref2e2.id = 740;
52. Код и runtime: аллокации
• ValueType вместо ReferenceType, где уместно.
53. Код и runtime: аллокации
• ValueType вместо ReferenceType, где уместно.
• Меньше временных объектов, аллоцируй однажды и переиспользуй.
54. Код и runtime: аллокации
Refactor
public static class Modifiers
{
public List<Modifier> GetAll()
{
var tmp = new List<Modifier>();
FillStuff(tmp);
return tmp;
}
}
public static class Modifiers
{
public void GetAll(List<Modifier> to_fill)
{
to_fill.Clear();
FillStuff(to_fill);
}
}
public void Update()
{
List<Modifier> modifiers = Modifiers.GetAll();
DisplayModifiers(modifiers);
}
List<Modifier> = new List<Modifier>(CAPACITY);
public void Update()
{
Modifiers.GetAll(modifiers);
DisplayModifiers(modifiers);
}
55. Код и runtime: аллокации
• ValueType вместо ReferenceType, где уместно.
• Меньше временных объектов, аллоцируй однажды и переиспользуй.
• «Гибридные» контейнеры.
56. Код и runtime: аллокации
struct HList<T> : IList<T>
гибридный контейнер
T val0;
T val1;
T val2;
T val3;
List<T> fallback; T TT ...
myHList.Add(newVal);
Count >
Capacity?
True,
alloc fallback once
False,
no allocs
58. Код и runtime: неявные аллокации
• Regex;
• “Excessive” + “ strings “ + “concat”;
59. Код и runtime: неявные аллокации
• Regex;
• “Excessive” + “ strings “ + “concat”;
• Closures & anon methods;
public void LoginWithID(int id)
{
if(IsLoggedIn())
return;
LoginWithDelegate(
delegate() { ProcessNewID(id); }
);
}
Вы ещё здесь…
… а эти объекты
уже созданы в heap.
“id” используется в closure,
копия создаётся в heap.
64. Код и runtime: boxing
struct Entry : IPrintable
Thread stack
var e1 = new Entry();
Entry
Managed heapvoid MyPrint(IPrintable p)
Object (boxed Entry)
IPrintable toPrint = e1;
MyPrint(toPrint); IPrintable ref1
Неявная аллокация
65. Код и runtime: аллокации Unity API
SkinnedMeshRenderer smr = ...;
for(int i = 0; i < smr.bones.Length; i++)
{
//... Do stuff
}
SkinnedMeshRenderer smr = ...;
var bones = smr.bones;
for(int i = 0; i < bones.Length; i++)
{
//... Do stuff
}
66. Код и runtime: аллокации Unity API
SkinnedMeshRenderer smr = ...;
for(int i = 0; i < smr.bones.Length; i++)
{
//... Do stuff
}
SkinnedMeshRenderer smr = ...;
var bones = smr.bones;
for(int i = 0; i < bones.Length; i++)
{
//... Do stuff
}
Аллоцирует массив “bones”
bones.Length раз
Аллоцирует массив “bones”
1 раз
67. Код и runtime: аллокации Unity API
• Если Unity API возвращает массив, будет создана новая копия.
68. Код и runtime: аллокации Unity API
• Если Unity API возвращает массив, будет создана новая копия.
• Каждый раз.
69. Код и runtime: аллокации Unity API
• Если Unity API возвращает массив, будет создана новая копия.
• Каждый раз.
• Даже если значение не изменилось.
70. Код и runtime: аллокации Unity API
• Если Unity API возвращает массив, будет создана новая копия.
• Каждый раз.
• Даже если значение не изменилось.
• Даже если это выглядит как безобидный getter.
71. Код и runtime: аллокации Unity API
• Если Unity API возвращает массив, будет создана новая копия.
• Каждый раз.
• Даже если значение не изменилось.
• Даже если это выглядит как безобидный getter.
• «Безобидный getter» может скрывать внутри тяжёлые вычисления.
public string name {
get {
return BadWordsFilter.ReplaceAll(data.name.Unescape());
}
}
72. Код и runtime: прочее
• No inlining.
• Вызов метода С# = вызов в машинном коде.
• Property accessors = вызов метода.
• Это сказывается на скорости интенсивных вычислений.
• Do inlining yourself.
73. Код и runtime: прочее
• К свойствам некоторых компонентов можно обращаться по
имени (Animator, Shader, Material).
• Внутри имя каждый раз преобразовывается в хэш.
• Вычисли хэш однажды и переиспользуй.
material.SetColor(“_Color”, Color.white);
animator.SetTrigger(“attack”);
static readonly int HASH_MAT_COLOR = Shader.PropertyToID(“_Color”);
static readonly int HASH_ANIM_ATTACK = Animator.StringToHash(“attack”);
material.SetColor(HASH_MAT_COLOR, Color.white);
animator.SetTrigger(HASH_ANIM_ATTACK);
74. Код и runtime: прочее
• Reflection is slow.
• Text parsing is slow.
• Text parsers based on Reflection are super slow.
83. Summary
• Специфика мобильных платформ должна учитываться на всех этапах
разработки.
• Экономить RAM не менее важно, чем ресурс CPU и GPU.
84. Summary
• Специфика мобильных платформ должна учитываться на всех этапах
разработки.
• Экономить RAM не менее важно, чем ресурс CPU и GPU.
• Разумный компромисс «Качество vs. Память» для ассетов, сжатие.
85. Summary
• Специфика мобильных платформ должна учитываться на всех этапах
разработки.
• Экономить RAM не менее важно, чем ресурс CPU и GPU.
• Разумный компромисс «Качество vs. Память» для ассетов, сжатие.
• Избегай overdraw.
86. Summary
• Специфика мобильных платформ должна учитываться на всех этапах
разработки.
• Экономить RAM не менее важно, чем ресурс CPU и GPU.
• Разумный компромисс «Качество vs. Память» для ассетов, сжатие.
• Избегай overdraw.
• Избегай аллокаций и boxing’а.
87. Summary
• Специфика мобильных платформ должна учитываться на всех этапах
разработки.
• Экономить RAM не менее важно, чем ресурс CPU и GPU.
• Разумный компромисс «Качество vs. Память» для ассетов, сжатие.
• Избегай overdraw.
• Избегай аллокаций и boxing’а.
• Помни особенности Unity Mono Runtime. («heap never shrinks»)
88. Summary
• Специфика мобильных платформ должна учитываться на всех этапах
разработки.
• Экономить RAM не менее важно, чем ресурс CPU и GPU.
• Разумный компромисс «Качество vs. Память» для ассетов, сжатие.
• Избегай overdraw.
• Избегай аллокаций и boxing’а.
• Помни особенности Unity Mono Runtime. («heap never shrinks»)
• Помни особенности Unity API. («no inlining», «arrays always allocated»)
89. Summary
• Специфика мобильных платформ должна учитываться на всех этапах
разработки.
• Экономить RAM не менее важно, чем ресурс CPU и GPU.
• Разумный компромисс «Качество vs. Память» для ассетов, сжатие.
• Избегай overdraw.
• Избегай аллокаций и boxing’а.
• Помни особенности Unity Mono Runtime. («heap never shrinks»)
• Помни особенности Unity API. («no inlining», «arrays always allocated»)
• Профайлить надо. Часто. Через Instruments.