Burst Hint Likely/Unlikely interesting results (837954)

Originally posted here: Burst Hint Likely/Unlikely interesting results (837953)
Because I forget there is a specific burst forum completely separate to DOTS.

Doing some profiling using the Hint Likely/Unlikely intrinsic and I’m not sure what is going on here.

The code

public int GetClosestSegmentNew(float3 position)
{
    // Find the first point in the closest line segment to the path
    float closestDist = DistToSegment(position, this.Path[0].Position, this.Path[1].Position);
    int closestSegment = 0;

    for (int i = 1; i < this.Length - 1; i++)
    {
        float dist = DistToSegment(position, this.Path[i].Position, this.Path[i + 1].Position);

        if (Hint.Unlikely(dist <= closestDist))
        {
            closestDist = dist;
            closestSegment = i;
        }
    }

    return closestSegment;
}

Using Unlikely hint - the correct choice

24.22 without hint
20.97 with hint
A pretty significant improvement

Using Likely hint - the wrong hint

Ok now the results are weird. How is it also faster!? It’s not as fast (and I repeat this test a lot of times to confirm and it’s consistently a little slower than Unlikely) but it’s still significantly faster than not having a hint. How?

and just to confirm it’s not something odd, removing the hint.

Both tests run the same speed as expected.

So the bad and naive TLDR would be, just add hints to every condition and things will run faster!
What I’m trying to understand is what is going on here and how both likely and unlikely on a condition could be faster.

2 Likes

Hi @tertle - that’s an interesting finding! Please could you grab the assembly from the two versions (using Burst Inspector) and paste it here?

I don’t have the original test but I still have the code unchanged it was based off and just throwing it in a simple job like this (which I believe was identical to what I did for the test except a bunch of iterations.)

this.Job.WithCode(() =>
    {
        var p = random.NextFloat3();
        result.Value = linePath.GetClosestSegment(p);
    })
    .WithName("Unlikely")
    .Run();

this.Job.WithCode(() =>
    {
        var p = random.NextFloat3();
        result.Value = linePath.GetClosestSegment2(p);
    })
    .WithName("Likely")
    .Run();

this.Job.WithCode(() =>
    {
        var p = random.NextFloat3();
        result.Value = linePath.GetClosestSegment3(p);
    })
    .WithName("None")
    .Run();

nets these 3 sets of assembly output from burst inspector

-edit- actually i do seem to have most of the original test sitting around. I’d just have to setup the alternate cases again but if you want to try repo the performance for yourself let me know and I can maybe bundle it up and send it to you privately.

7105384–847237–likely.txt (28.2 KB)
7105384–847240–none.txt (28 KB)
7105384–847243–unlikely.txt (28.3 KB)

I’m not that great at grokking assembly and I’m only meant to be taking a 5 minute break so I haven’t checked this in detail in your case, but one possible explanation for both likely and unlikely hints speeding up code is that they both act as hints that the branch is predictable.

If the compiler assumes by default that the branch is unpredictable, it will probably create branch free code using masking or conditional moves. This may be less efficient than a well predicted branch that almost always completely skips the work. Telling it that it’s either likely or unlikely will encourage it to compile to an actual branch.

p.s. if you’re optimising that code, you could probably switch from distance to squared distance and eliminate the sqrts in the tight loop.

1 Like

Good observation. If I recall correctly I think the distance value used to be returned and used which is why it was used. However it is not anymore so there is no reason to not remove the sqrt.

You could still work in squared distances if you’re returning it! Keep it squared during the loop, then sqrt once when returning.