Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Burst calculation weirdness. Vectorization issue?

Discussion in 'Scripting' started by azmi_unity, Aug 28, 2021.

  1. azmi_unity

    azmi_unity

    Joined:
    Dec 13, 2020
    Posts:
    62
    I was trying to calculate angular acceleration times needed to rotate an object (say a ship for example), but they spin out of control when WithBurst() was used, WithoutBurst() works just fine.

    Trying to find the cause, I discovered the following:
    1. Debug.Log itself changes the outcome of the calculation.
    2. Moving private static readonly fields into the job changes the outcome too.

    The following was gutted out from a larger system.

    Code (CSharp):
    1. using Unity.Entities;
    2. using Unity.Jobs;
    3. using UnityEngine;
    4. using Unity.Physics.Systems;
    5. using Unity.Physics;
    6. using Unity.Mathematics;
    7. using Unity.Collections;
    8.  
    9. /// <summary>
    10. /// Azmi ~ 20210825
    11. /// </summary>
    12. [UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
    13. [UpdateBefore(typeof(BuildPhysicsWorld))]
    14. [AlwaysUpdateSystem]
    15. public class BuggedThrusterSystem : SystemBase
    16. {
    17.     private static readonly quaternion PitchCW;
    18.     private static readonly quaternion PitchCCW;
    19.     private static readonly quaternion YawCW;
    20.     private static readonly quaternion YawCCW;
    21.     private static readonly quaternion RollCW;
    22.     private static readonly quaternion RollCCW;
    23.  
    24.  
    25.     static BuggedThrusterSystem()
    26.     {
    27.         PitchCW = quaternion.EulerZXY(0, 0, 0);
    28.         PitchCCW = quaternion.EulerZXY(math.PI, 0, 0);
    29.         RollCW = quaternion.EulerZXY(0, -math.PI / 2f, 0);
    30.         RollCCW = quaternion.EulerZXY(0, +math.PI / 2f, 0);
    31.         YawCW = quaternion.EulerZXY(-math.PI / 2f, 0, 0);
    32.         YawCCW = quaternion.EulerZXY(+math.PI / 2f, 0, 0);
    33.     }
    34.  
    35.  
    36.     protected override void OnUpdate()
    37.     {
    38.         NativeArray<float> result = new NativeArray<float>(1, Allocator.TempJob);           // Dummy array, without this Burst optimizes the job to blank
    39.  
    40.         // Calculate without burst
    41.         var vector = new float3(-1, 0, 0);                  // any vector
    42.         var velocity = new float3(10, 20, 0);                  // any vector
    43.         result[0] = BuggedCalculateAngularBurnSolution(vector, velocity, 5f);
    44.  
    45.         Job
    46.             .WithBurst()
    47.             .WithCode(() =>
    48.             {
    49.                 var vector = new float3(-1, 0, 0);
    50.                 var velocity = new float3(10, 20, 0);
    51.                 result[0] = BuggedCalculateAngularBurnSolution(vector, velocity, 5f);
    52.  
    53.             })
    54.             .WithDisposeOnCompletion(result)
    55.             .Schedule();
    56.     }
    57.  
    58.  
    59.  
    60.     static float BuggedCalculateAngularThrustBurnTime(quaternion inverse, float3 required, float3 angularVelocity, float angularAcceleration)
    61.     {
    62.         float3 localAdjust = math.mul(inverse, required + angularVelocity);
    63.         float3 localVelocity = math.mul(inverse, angularVelocity);
    64.  
    65.         float I = localAdjust.z;        // Angular distance to travel
    66.         float Vi = localVelocity.z;     // Initial angular velocity per second
    67.  
    68.         float a = angularAcceleration;   // Acceleration
    69.  
    70.         float Vm2 = ((2 * I * a) + (Vi * Vi)) / 2;
    71.  
    72.         //Debug.Log($"Sample: {I} {Vi} {a} {Vm2}");
    73.  
    74.         if (Vm2 < 0)
    75.             return 0;
    76.         float Vm = math.sqrt(Vm2);
    77.  
    78.         return (Vm - Vi) / a;
    79.     }
    80.  
    81.     static void BuggedCalculateAngularThrustBurnTime2(quaternion cwInverse, quaternion ccwInverse, float3 required, float3 angularVelocity, float angularAcceleration, out float burnTime, out bool burnCW)
    82.     {
    83.         float cwTime = BuggedCalculateAngularThrustBurnTime(cwInverse, required, angularVelocity, angularAcceleration);
    84.         float ccwTime = BuggedCalculateAngularThrustBurnTime(ccwInverse, required, angularVelocity, angularAcceleration);
    85.  
    86.         if (cwTime > ccwTime)
    87.         {
    88.             burnTime = cwTime;
    89.             burnCW = true;
    90.         }
    91.         else
    92.         {
    93.             burnTime = ccwTime;
    94.             burnCW = false;
    95.         }
    96.     }
    97.  
    98.     static float BuggedCalculateAngularBurnSolution(float3 vector, float3 angularVelocity, float acceleration)
    99.     {
    100.         // Calculate thruster burn times
    101.         float pitchBurnTime;
    102.         bool pitchBurnCW;
    103.         float rollBurnTime;
    104.         bool rollBurnCW;
    105.         float yawBurnTime;
    106.         bool yawBurnCW;
    107.  
    108.         BuggedCalculateAngularThrustBurnTime2(PitchCCW, PitchCW, vector, angularVelocity, acceleration, out pitchBurnTime, out pitchBurnCW);
    109.         BuggedCalculateAngularThrustBurnTime2(RollCCW, RollCW, vector, angularVelocity, acceleration, out rollBurnTime, out rollBurnCW);
    110.         BuggedCalculateAngularThrustBurnTime2(YawCCW, YawCW, vector, angularVelocity, acceleration, out yawBurnTime, out yawBurnCW);
    111.  
    112.         Debug.Log($"Calc: {vector} {pitchBurnCW} {pitchBurnTime} {rollBurnCW} {rollBurnTime} {yawBurnCW} {yawBurnTime}");
    113.  
    114.         return pitchBurnTime + rollBurnTime + yawBurnTime;
    115.     }
    116.  
    117.  
    118. }
    119.  
    Here is the result of the calculation as read by Debug.Log:

    Code (csharp):
    1.  
    2. Calc: float3(-1f, 0f, 0f) False 0 True 2.447214 False 6
    3. Calc: float3(-1f, 0f, 0f) True 5.969601E-07 True 3.414214 False 6.810694
    4.  
    The first line is without burst run from the main thread, the second line is from the bursted job, I think the discrepancy is way to large to attribute it to float precision?

    Uncommenting the first Debug.Log fixes the issue?!

    Reproduceable in Burst 1.4.1 and the latest 1.4.11 too.
     
  2. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,070
    You have racing condition somewhere.
    Getting different results when Debug.Log is off doesn't mean Debug.Log messes with the calculation.
    It means that your framerate is different, because Console output isn't exactly cheap.
     
  3. azmi_unity

    azmi_unity

    Joined:
    Dec 13, 2020
    Posts:
    62
    I'm not sure how a race condition can happen here. The code above doesn't access any shared memory. And it's frame independent as all the input is hardcoded, so the outcome should be the same regardless of burst being used or not.
     
  4. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,070
    Maybe I'm wrong, but race condition is around 1M times more likely than
    Take it or leave it.
     
  5. azmi_unity

    azmi_unity

    Joined:
    Dec 13, 2020
    Posts:
    62
    Upgrading to Unity 2021.1.18f1 and Burst 1.5.6 fixes it for me.
     
  6. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,070
    Great to hear