Burst calculation weirdness. Vectorization issue?

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.

using Unity.Entities;
using Unity.Jobs;
using UnityEngine;
using Unity.Physics.Systems;
using Unity.Physics;
using Unity.Mathematics;
using Unity.Collections;

/// <summary>
/// Azmi ~ 20210825
/// </summary>
[UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
[UpdateBefore(typeof(BuildPhysicsWorld))]
[AlwaysUpdateSystem]
public class BuggedThrusterSystem : SystemBase
{
    private static readonly quaternion PitchCW;
    private static readonly quaternion PitchCCW;
    private static readonly quaternion YawCW;
    private static readonly quaternion YawCCW;
    private static readonly quaternion RollCW;
    private static readonly quaternion RollCCW;


    static BuggedThrusterSystem()
    {
        PitchCW = quaternion.EulerZXY(0, 0, 0);
        PitchCCW = quaternion.EulerZXY(math.PI, 0, 0);
        RollCW = quaternion.EulerZXY(0, -math.PI / 2f, 0);
        RollCCW = quaternion.EulerZXY(0, +math.PI / 2f, 0);
        YawCW = quaternion.EulerZXY(-math.PI / 2f, 0, 0);
        YawCCW = quaternion.EulerZXY(+math.PI / 2f, 0, 0);
    }


    protected override void OnUpdate()
    {
        NativeArray<float> result = new NativeArray<float>(1, Allocator.TempJob);           // Dummy array, without this Burst optimizes the job to blank

        // Calculate without burst
        var vector = new float3(-1, 0, 0);                  // any vector
        var velocity = new float3(10, 20, 0);                  // any vector
        result[0] = BuggedCalculateAngularBurnSolution(vector, velocity, 5f);

        Job
            .WithBurst()
            .WithCode(() =>
            {
                var vector = new float3(-1, 0, 0);
                var velocity = new float3(10, 20, 0);
                result[0] = BuggedCalculateAngularBurnSolution(vector, velocity, 5f);

            })
            .WithDisposeOnCompletion(result)
            .Schedule();
    }



    static float BuggedCalculateAngularThrustBurnTime(quaternion inverse, float3 required, float3 angularVelocity, float angularAcceleration)
    {
        float3 localAdjust = math.mul(inverse, required + angularVelocity);
        float3 localVelocity = math.mul(inverse, angularVelocity);

        float I = localAdjust.z;        // Angular distance to travel
        float Vi = localVelocity.z;     // Initial angular velocity per second

        float a = angularAcceleration;   // Acceleration

        float Vm2 = ((2 * I * a) + (Vi * Vi)) / 2;

        //Debug.Log($"Sample: {I} {Vi} {a} {Vm2}");

        if (Vm2 < 0)
            return 0;
        float Vm = math.sqrt(Vm2);

        return (Vm - Vi) / a;
    }

    static void BuggedCalculateAngularThrustBurnTime2(quaternion cwInverse, quaternion ccwInverse, float3 required, float3 angularVelocity, float angularAcceleration, out float burnTime, out bool burnCW)
    {
        float cwTime = BuggedCalculateAngularThrustBurnTime(cwInverse, required, angularVelocity, angularAcceleration);
        float ccwTime = BuggedCalculateAngularThrustBurnTime(ccwInverse, required, angularVelocity, angularAcceleration);

        if (cwTime > ccwTime)
        {
            burnTime = cwTime;
            burnCW = true;
        }
        else
        {
            burnTime = ccwTime;
            burnCW = false;
        }
    }

    static float BuggedCalculateAngularBurnSolution(float3 vector, float3 angularVelocity, float acceleration)
    {
        // Calculate thruster burn times
        float pitchBurnTime;
        bool pitchBurnCW;
        float rollBurnTime;
        bool rollBurnCW;
        float yawBurnTime;
        bool yawBurnCW;

        BuggedCalculateAngularThrustBurnTime2(PitchCCW, PitchCW, vector, angularVelocity, acceleration, out pitchBurnTime, out pitchBurnCW);
        BuggedCalculateAngularThrustBurnTime2(RollCCW, RollCW, vector, angularVelocity, acceleration, out rollBurnTime, out rollBurnCW);
        BuggedCalculateAngularThrustBurnTime2(YawCCW, YawCW, vector, angularVelocity, acceleration, out yawBurnTime, out yawBurnCW);

        Debug.Log($"Calc: {vector} {pitchBurnCW} {pitchBurnTime} {rollBurnCW} {rollBurnTime} {yawBurnCW} {yawBurnTime}");

        return pitchBurnTime + rollBurnTime + yawBurnTime;
    }


}

Here is the result of the calculation as read by Debug.Log:

Calc: float3(-1f, 0f, 0f) False 0 True 2.447214 False 6
Calc: float3(-1f, 0f, 0f) True 5.969601E-07 True 3.414214 False 6.810694

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.

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.

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.

Maybe I’m wrong, but race condition is around 1M times more likely than

Take it or leave it.

Upgrading to Unity 2021.1.18f1 and Burst 1.5.6 fixes it for me.

Great to hear