# Discussion Is using Vectors crippling performance? Should we avoid them and roll our own vector systems??

Discussion in 'Editor & General Support' started by SoftwareGeezers, Aug 2, 2022.

1. ### SoftwareGeezers

Joined:
Jun 22, 2013
Posts:
731
I've just been profiling a high-intensity game scenario and have stumbled upon some, for me, shocking revelation about Vector performance where it may be 5 times slower than the optimal alternatives, the complete opposite of what I was expecting assuming CPU vector-unit acceleration of the class.

In short, a nested loop of 250,000 simple subtraction operations, a scenario that should fit CPU caches perfectly and get optimised processing, is taking 7ms when using Vector 2's 'add' operation, 2ms using a custom class fields, and < 1ms using the individual fields of a struct:

Exact results elapsed time in ticks:

(VecA) Vector methods: 131227
(VecB) Vector fields: 24327
(Str) Struct fields: 24556
(ClassA) Class fields: 25535
(ClassB) Class methods: 64918

relative performance:
VecA = 1
ClassB = 0.4831704
ClassA = 0.196042
Str = 0.1856325
VecB = 0.1853811

Or normalised for the fastest
VecB = 1
Str = 1
ClassA = 1.057
ClassB = 2.606
VecA = 5.393

Accessing the Vector2's fields and processing their data directly is the fastest manipulation available, far faster than using the Vector2 methods.

Ultimately, where does this leave the value of Vector classes, and potentially many other aspects to Unity, in performance games? Vector maths is everywhere, and the possibility that all our code might be as much as 5x slower as a result strikes me as pretty serious.

Here's my test script. I create results arrays for each method and compare the results at the end to ensure they are the same (which led me to find semantic bugs when they weren't!). There are more tests I can do like where the bottlenecks lie, in assignment or maths, etc. but I'm done for now!

Code (CSharp):
1. using UnityEngine;
2. using System.Diagnostics;
3.
4. public class test : MonoBehaviour {
5.     Stopwatch sw = new Stopwatch();
6.
7.     struct coord {
8.         public float x;
9.         public float y;
10.     }
11.
12.     class coordinate {
13.         public float x;
14.         public float y;
15.         public coordinate(float _x, float _y) {
16.             x = _x;
17.             y = _y;
18.         }
19.         public void Set(coordinate c) {
20.             x = c.x;
21.             y = c.y;
22.         }
23.         public void Set(float _x, float _y) {
24.             x = _x;
25.             y = _y;
26.         }
27.
28.         public void Add(float _x, float _y) {
29.             x += _x;
30.             y += _y;
31.         }
32.
33.         public void Add(coordinate c) {
34.             x += c.x;
35.             y += c.y;
36.         }
37.         public void Subtract(coordinate c) {
38.             x -= c.x;
39.             y -= c.y;
40.         }
41.
42.         public float SqrMagnitude() {
43.             return x * x + y * y;
44.         }
45.     }
46.
47.     Vector2[] positions_vector = new Vector2;
48.     Vector2[] results_vector_method = new Vector2;
49.     Vector2[] results_vector_fields = new Vector2;
50.
51.     coord[] positions_struct = new coord;
52.     coord[] results_struct = new coord;
53.
54.     coordinate[] coordinates_class = new coordinate;
55.     coordinate[] results_class_method = new coordinate;
56.     coordinate[] results_class_field = new coordinate ;
57.
58.     void Start() {
59.         float _x, _y;
60.         for (int n = 0; n < 500; n++) {
61.             _x = Random.Range(0f, 5f);
62.             _y = Random.Range(0f, 5f);
63.             positions_vector[n] = new Vector2(_x, _y);
64.             results_vector_method[n] = Vector2.zero;
65.             results_vector_fields[n] = Vector2.zero;
66.             positions_struct[n] = new coord();
67.             positions_struct[n].x = _x;
68.             positions_struct[n].y = _y;
69.             results_struct[n] = new coord();
70.
71.             coordinates_class[n] = new coordinate(_x, _y);
72.             results_class_method[n] = new coordinate(0, 0);
73.             results_class_field[n] = new coordinate(0, 0);
74.         }
75.     }
76.
77.     private void Update() {
78.         if (Input.GetKeyDown(KeyCode.Space)) {
79.             Benchmark();
80.         }
81.     }
82.
83.     void Benchmark() {
84.         // Reset everything
85.         float timeTicksVecA = 0;
86.         float timeTicksVecB = 0;
87.         float timeTicksStruct = 0;
88.         float timeTicksClassA = 0;
89.         float timeTicksClassB = 0;
90.
91.         UnityEngine.Debug.ClearDeveloperConsole();
92.         for (int n = 0; n < 500; n++) {
93.             results_vector_method[n] = Vector2.zero;
94.             results_vector_fields[n] = Vector2.zero;
95.             results_struct[n].x = 0;
96.             results_struct[n].y = 0;
97.             results_class_method[n].Set(0, 0);
98.             results_class_field[n].Set(0, 0);
99.         }
100.
101.         float sqrMagnitude;
102.
103.
104.
105.         // Vector methods
106.         sw.Start();
107.         Vector2 delta = Vector2.zero;
108.         for (int n = 0; n < 500; n++) {
109.             for (int m = 0; m < 500; m++) {
110.                 delta = positions_vector[n] - positions_vector[m];
111.                 sqrMagnitude = delta.sqrMagnitude;
112.                 if (sqrMagnitude < 1) {
113.                     results_vector_method[n] += delta;
114.                 }
115.             }
116.         }
117.         sw.Stop();
118.         timeTicksVecA = sw.ElapsedTicks;
119.         UnityEngine.Debug.Log("Vector subtraction = " + timeTicksVecA + "   ms = "+sw.ElapsedMilliseconds);
120.         sw.Reset();
121.
122.
123.         // Vector field processing
124.         sw.Start();
125.         float accumulatedx, accumulatedy;
126.         for (int n = 0; n < 500; n++) {
127.             accumulatedx = 0;
128.             accumulatedy = 0;
129.             for (int m = 0; m < 500; m++) {
130.                 float deltax = positions_vector[n].x - positions_vector[m].x;
131.                 float deltay = positions_vector[n].y - positions_vector[m].y;
132.                 sqrMagnitude = deltax * deltax + deltay * deltay;
133.                 if (sqrMagnitude < 1) {
134.                     accumulatedx += deltax;
135.                     accumulatedy += deltay;
136.                 }
137. //                delta.Set(deltax, deltay);
138.             }
139.             results_vector_fields[n].Set(results_vector_fields[n].x + accumulatedx, results_vector_fields[n].y + accumulatedy);
140.         }
141.         sw.Stop();
142.         timeTicksVecB = sw.ElapsedTicks;
143.         UnityEngine.Debug.Log("Vector components = " + timeTicksVecB + "   ms = " + sw.ElapsedMilliseconds);
144.         sw.Reset();
145.
146.
147.         // structure
148.         sw.Start();
149.         coord output_struct = new coord();
150.         for (int n = 0; n < 500; n++) {
151.             for (int m = 0; m < 500; m++) {
152.                 output_struct.x = positions_struct[n].x - positions_struct[m].x;
153.                 output_struct.y = positions_struct[n].y - positions_struct[m].y;
154.                 sqrMagnitude = output_struct.x * output_struct.x + output_struct.y * output_struct.y;
155.                 if (sqrMagnitude < 1) {
156.                     results_struct[n].x += output_struct.x;
157.                     results_struct[n].y += output_struct.y;
158.                 }
159.             }
160.         }
161.         sw.Stop();
162.         timeTicksStruct = sw.ElapsedTicks;
163.         UnityEngine.Debug.Log("Structure = " + timeTicksStruct + "   ms = " + sw.ElapsedMilliseconds);
164.         sw.Reset();
165.
166.
167.         // class field processing
168.         sw.Start();
169.         coordinate output_class = new coordinate(0,0);
170.         for (int n = 0; n < 500; n++) {
171.             for (int m = 0; m < 500; m++) {
172.                 float deltax = coordinates_class[n].x - coordinates_class[m].x;
173.                 float deltay = coordinates_class[n].y - coordinates_class[m].y;
174.                 sqrMagnitude = deltax * deltax + deltay * deltay;
175.                 if (sqrMagnitude < 1) {
176.                     results_class_field[n].x += deltax;
177.                     results_class_field[n].y += deltay;
178.                 }
179.             }
180.         }
181.         sw.Stop();
182.         timeTicksClassA = sw.ElapsedTicks;
183.         UnityEngine.Debug.Log("Class = " + timeTicksClassA + "   ms = " + sw.ElapsedMilliseconds);
184.         sw.Reset();
185.
186.
187.         // class method
188.         sw.Start();
189.         coordinate c = new coordinate(0, 0);
190.         coordinate c_delta = new coordinate(0, 0);
191.         for (int n = 0; n < 500; n++) {
192.             c.Set(coordinates_class[n]);
193.             for (int m = 0; m < 500; m++) {
194.                 c_delta.Set(c);
195.                 c_delta.Subtract(coordinates_class[m]);
196.                 if (c_delta.SqrMagnitude() < 1) {
198.                 }
199.             }
200.         }
201.         sw.Stop();
202.         timeTicksClassB = sw.ElapsedTicks;
203.         UnityEngine.Debug.Log("Class method = " + timeTicksClassB + "   ms = " + sw.ElapsedMilliseconds);
204.         sw.Reset();
205.
206.
207.         // final comparison
208.         print("result confirmation");
209.         for (int n = 50; n < 500; n += 50) {
210.             print("VecA " + results_vector_method[n].x.ToString("F2") + "," + results_vector_method[n].y.ToString("F2") +
211.                 "    VecB " + results_vector_fields[n].x.ToString("F2") + "," + results_vector_fields[n].y.ToString("F2") +
212.                 "    Str " + results_struct[n].x.ToString("F2") + "," + results_struct[n].y.ToString("F2") +
213.                 "    ClassA " + results_class_field[n].x.ToString("F2") + "," + results_class_field[n].y.ToString("F2") +
214.                 "    ClassB " + results_class_method[n].x.ToString("F2") + "," + results_class_method[n].y.ToString("F2")
215.                 );
216.         }
217.
218.         float longestTime = timeTicksVecA;
219.         longestTime = Mathf.Max(longestTime, timeTicksVecB);
220.         longestTime = Mathf.Max(longestTime, timeTicksStruct);
221.         longestTime = Mathf.Max(longestTime, timeTicksClassA);
222.         longestTime = Mathf.Max(longestTime, timeTicksClassB);
223.
224.         print("relative performance VecA " + (timeTicksVecA / longestTime) +
225.             "   VecB " + (timeTicksVecB / longestTime) +
226.             "   Str " + (timeTicksStruct / longestTime) +
227.             "   ClassA " + (timeTicksClassA / longestTime) +
228.             "   ClassB " + (timeTicksClassB / longestTime)
229.             );
230.     }
231. }

Joined:
Aug 3, 2010
Posts:
8,252