Бесплатная дополнительная оптимизация кода в Unity

Что если мы уже оптимизировали код, но хотим его ускорить еще немного? Есть решение.

Idea

Начиная с FW4.6 C# поддерживает рекомендации по инлайнингу для компилятора. Результат не обязательно будет заинлайнен, это именно рекомендация, но в общем случае оно прекрасно работает. Для активации мы должны добавить специальный атрибут к каждому методу, который хотим заинлайнить, а так же убедиться, что используем FW4.6 профиль для компилятора:

1
2
3
4
5
6
#if NET_4_6
[System.Runtime.CompilerServices.MethodImpl (System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
#endif
public T GetComponent<T> (int entity) where T : class, new () {
var entityData = _entities[entity];
...

Директива препроцессора #if NET_4_6 позволяет использовать данный код и на FW3.5-профиле, т.е обеспечивает обратную совместимость (разумеется, с потерей инлайнинга).

Тестовый код

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
using System.Collections;
using UnityEngine;
namespace LeopotamGroup.Ecs.Tests {
public class Test8 : MonoBehaviour, IEcsInitSystem {
class C8_1 { }
class C8_2 { }

[EcsWorld]
EcsWorld _world;

IEnumerator Start () {
yield return new WaitForSeconds (2f);
var systems = new EcsSystems (new EcsWorld ()).Add (this);
systems.Initialize ();
systems.Destroy ();
}

void IEcsInitSystem.Initialize () {
const int TESTS = 40;
const int T = 10000000;
var sw = new System.Diagnostics.Stopwatch ();
var result = 0;
C8_1 c1;
C8_2 c2;
var entity = _world.CreateEntity ();
_world.AddComponent<C8_1> (entity);
_world.AddComponent<C8_2> (entity);

for (var test = 0; test < TESTS; test++) {
sw.Reset ();
sw.Start ();
for (var i = 0; i < T; i++) {
c1 = _world.GetComponent<C8_1> (entity);
c2 = _world.GetComponent<C8_2> (entity);
}
sw.Stop ();
result += sw.Elapsed.Milliseconds;
}
Debug.LogFormat ("GetComponent: {0}", result / (float) TESTS);
}

void IEcsInitSystem.Destroy () { }
}
}

Результаты

Без инлайнинга:

1
GetComponent: 188.125

С инлайнингом:

1
GetComponent: 134.225

Примерно 30% ускорения, причем бесплатно. Ecs фреймворк работает все быстрее и быстрее. :)