Search Unity

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

    SoftwareGeezers

    Joined:
    Jun 22, 2013
    Posts:
    902
    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[500];
    48.     Vector2[] results_vector_method = new Vector2[500];
    49.     Vector2[] results_vector_fields = new Vector2[500];
    50.  
    51.     coord[] positions_struct = new coord[500];
    52.     coord[] results_struct = new coord[500];
    53.  
    54.     coordinate[] coordinates_class = new coordinate[500];
    55.     coordinate[] results_class_method = new coordinate[500];
    56.     coordinate[] results_class_field = new coordinate [500];
    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) {
    197.                     results_class_method[n].Add(c_delta);
    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. }
     
  2. mgear

    mgear

    Joined:
    Aug 3, 2010
    Posts:
    9,408