Performance tests of instance creation

Performance - main goal for my ECS framework and I tried to find fastest way yo create new instance of type. Standard behaviour with Activator.CreateInstance works as must, but lot of people told me that it’s not fastest method for instancing. Lets try to find, what can be faster?

Performance - main goal for my ECS framework and I tried to find fastest way yo create new instance of type. Standard behaviour with Activator.CreateInstance works as must, but lot of people told me that it’s not fastest method for instancing. Lets try to find, what can be faster?

Test environment

We will run 100000 iterations and will repeat this process 30 times, then will take average result time. We will measure time for creating new instance of specified type with assigning to typed variable at each implementation. About additional issues with testing for Unity engine you can read in “Performance tests of Event, ActionList, Observer, InterfaceObserver“, same rules will be used in this test too. Since we will work intensively with memory allocation, we should use garbage collecting before each implementation.

Test cases:

  • Activator.CreateInstance (Type)
    Creates instance with force casting to target type.

  • Activator.CreateInstance()
    Creates instance in Generic-style without force casting to target type.

  • new T () where T: new()
    Creates instance in Generic-style with new() constraint for calling default constructor.

  • ConstructorInfo.Invoke
    Creates instance with direct calling constructor method through Reflection with force casting to target type.

  • CustomActivator
    Creates instance with lambda method with direct instance creation of specified type.

Test source

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
class InstanceCreationPerformance : MonoBehaviour {
IEnumerator Start () {
yield return new WaitForSeconds (3f);

const int TESTS = 30;
const int T = 100000;
int result;
Test1 obj;
var sw = new System.Diagnostics.Stopwatch ();

result = 0;
for (var test = 0; test < TESTS; test++) {
GC.Collect ();
GC.WaitForPendingFinalizers ();
sw.Reset ();
sw.Start ();
for (var i = 0; i < T; i++) {
obj = (Test1) CreateInstance1 (typeof (Test1));
}
sw.Stop ();
obj = null;
result += sw.Elapsed.Milliseconds;
}
Debug.LogFormat ("Activator.CreateInstance(Type): {0}", result / (float) TESTS);

result = 0;
for (var test = 0; test < TESTS; test++) {
GC.Collect ();
GC.WaitForPendingFinalizers ();
sw.Reset ();
sw.Start ();
for (var i = 0; i < T; i++) {
obj = CreateInstance2<Test1> ();
}
sw.Stop ();
obj = null;
result += sw.Elapsed.Milliseconds;
}
Debug.LogFormat ("Activator.CreateInstance<T> (): {0}", result / (float) TESTS);

result = 0;
for (var test = 0; test < TESTS; test++) {
GC.Collect ();
GC.WaitForPendingFinalizers ();
sw.Reset ();
sw.Start ();
for (var i = 0; i < T; i++) {
obj = CreateInstance3<Test1> ();
}
sw.Stop ();
obj = null;
result += sw.Elapsed.Milliseconds;
}
Debug.LogFormat ("new T(): {0}", result / (float) TESTS);

result = 0;
for (var test = 0; test < TESTS; test++) {
GC.Collect ();
GC.WaitForPendingFinalizers ();
sw.Reset ();
sw.Start ();
for (var i = 0; i < T; i++) {
obj = CreateInstance4<Test1> ();
}
sw.Stop ();
result += sw.Elapsed.Milliseconds;
}
Debug.LogFormat ("Activator.CreateInstance(typeof(T)): {0}", result / (float) TESTS);

result = 0;
var ctor = typeof (Test1).GetConstructor (new Type[0]);
for (var test = 0; test < TESTS; test++) {
GC.Collect ();
GC.WaitForPendingFinalizers ();
sw.Reset ();
sw.Start ();
for (var i = 0; i < T; i++) {
obj = CreateInstance5<Test1> (ctor);
}
sw.Stop ();
result += sw.Elapsed.Milliseconds;
}
Debug.LogFormat ("Invoke(ConstructorInfo): {0}", result / (float) TESTS);

result = 0;
var activator = CustomActivator<Test1>.Instance;
activator.Bind (() => new Test1 ());
for (var test = 0; test < TESTS; test++) {
GC.Collect ();
GC.WaitForPendingFinalizers ();
sw.Reset ();
sw.Start ();
for (var i = 0; i < T; i++) {
obj = activator.CreateInstance ();
}
sw.Stop ();
result += sw.Elapsed.Milliseconds;
}
Debug.LogFormat ("CustomActivator: {0}", result / (float) TESTS);
}

class Test1 { }

object CreateInstance1 (Type type) {
return Activator.CreateInstance (type);
}

T CreateInstance2<T> () where T : class {
return Activator.CreateInstance<T> ();
}

T CreateInstance3<T> () where T : class, new () {
return new T ();
}

T CreateInstance4<T> () where T : class {
return (T) Activator.CreateInstance (typeof (T));
}

static object[] _noParams = new object[0];

T CreateInstance5<T> (System.Reflection.ConstructorInfo ctor) where T : class {
return (T) ctor.Invoke (_noParams);
}

class CustomActivator<T> where T : class, new () {
public static readonly CustomActivator<T> Instance = new CustomActivator<T> ();
Func<T> _factory;

public void Bind (Func<T> factory) {
_factory = factory;
}

public T CreateInstance () {
if (_factory != null) {
return _factory ();
} else {
return new T ();
}
}
}
}

Test results

1
2
3
4
5
6
7
8
// unity 2017.3.0f3, fw3.5 profile, 100k iterations, average of 30 tests.
// standalone macos 10.13.2, mono.
Activator.CreateInstance(Type): 61.93333
Activator.CreateInstance<T> (): 65.23333
new T(): 65.2
Activator.CreateInstance(typeof(T)): 63.26667
Invoke(ConstructorInfo): 44.03333
CustomActivator: 3

Both cases of Activator.CreateInstance are very close by performance. Generic-case slightly slower, but don’t forget that there are 100к iterations in synthetic environment without additional processing, pure loop only.

New T()-case equals to Activator.CreateInstance, because it based on it.

Activator.CreateInstance(typeof(T)) - this combination of speed from non-typed case of Activator.CreateInstance and protection from generic constraints. Good replacement for Activator.CreateInstance<T> () and new T().

Invoke(ConstructorInfo)-case shown very good performance for Reflection-based code. Good replacement for all previous cases.

Clear winner - direct creation of target type through lambda method, 20x times boost relative to Activator.CreateInstance! Yes, numbers found for synthetic environment, but in real project with additional processing it gave 2x times boost in creation of new instances of components.