3

I lately wrote a bit that shall smooth out the extends of a 0-1 range. Out of curiousity I measured the performance of two possibilities to acchieve the same end result. What came out is that this:

_ = (Mathf.Sin(-1.57079633f + (time * 3.14159265f)) + 1) * 0.5f; // Sin + 4o

faster than this

_ = Mathf.Cos(time * 3.14159265f) * -0.5f + 0.5f; // Cos + 3o

even though the former has more operations? And it's not faster by just some tiny bit, it's approximately 31% faster. For time t a constant of 0.5f was used.

There's no difference in using Sin or Cos for either, I did test for that in case the algorithm behind those functions would be the cause. Since Mathf is a Unity thing i tested it using System.Math. as well to make sure this isn't the cause, the results are roughly the same exept that System.Math. is overall a tiny bit slower since it calculates a double that then has to be cast to a float (Single).

Is Someone able to enlighten me why this is the case? I'd be interessted in undestanding why this is.

Addendum: Testing code

var sw = new System.Diagnostics.Stopwatch();
sw.Start();
for (int i = 0; i < 500000; i++)
{
    _ = (Mathf.Sin(-1.57079633f + (time * 3.14159265f)) + 1) * 0.5f; // Sin + 4o
}
sw.Stop();
print($"Sin calc time = {sw.ElapsedTicks * 0.0001f}ms ({sw.ElapsedMilliseconds}ms)");
sw.Restart();
for (int i = 0; i < 500000; i++)
{
    _ = Mathf.Cos(time * 3.14159265f) * -0.5f + 0.5f; // Cos + 3o
}
sw.Stop();
print($"Cos calc time = {sw.ElapsedTicks * 0.0001f}ms ({sw.ElapsedMilliseconds}ms)");
CBX_MG
  • 93
  • 8
  • 2
    It's worth noting that `Mathf` is a Unity thing, not a .NET thing – MindSwipe Jan 16 '23 at 09:12
  • You're right, edited it so it is mentioned. Tested it as well to exclude the possibility and used System.Math, the results are the same. – CBX_MG Jan 16 '23 at 09:21
  • I would suggest that you take a look at the IL generated by each. – jmcilhinney Jan 16 '23 at 09:27
  • `For time t a constant of 0.5f was used.` Do you mean that you had something like `const float time = 0.5f;`? If not, what *do* you mean? – Matthew Watson Jan 16 '23 at 09:44
  • Other than order of operations I can't really see a diference there. I don't see into `[System.Runtime]System.Math::Cos(float64)`/`[System.Runtime]System.Math::Sin(float64)` though. – CBX_MG Jan 16 '23 at 09:45
  • Did you just run them once each or did you run them many times and then use the average times? – YungDeiza Jan 16 '23 at 09:47
  • @Matthew Watson All three possible scenarios; I directly defined `0.5f` within the algorithm, I used a defined `const float time = 0.5f` and I used a regular field value. Same results in all cases. – CBX_MG Jan 16 '23 at 09:49
  • 1
    @YungDeiza I used a 500k itteraion for loop – CBX_MG Jan 16 '23 at 09:51
  • 3
    When using a `const` value of 0.5 for time, the .NET 7 compiler (and presumably earlier versions too) pre-calculates the parameter to pass to `Math.Sin()` and `Math.Cos()`. For the call the `Math.Sin()` this ends up calling `Math.Sin(0)` - and I suspect that the implementation of `Math.Sin()` has an optimisation for that case that just returns 0. – Matthew Watson Jan 16 '23 at 09:59
  • @MatthewWatson the logic is sound but why const is relevant? The value passed to Sin is 0 in any case, whether time is const or not. – Evk Jan 16 '23 at 10:06
  • @MatthewWatson You're absolutely right, I didn't think of that. Tested again and assigned a random value to `time` before starting the loop, this makes them about equivalent. Makes perfect sense since then the former is only one operation plus whatever happens in `Sin()`. – CBX_MG Jan 16 '23 at 10:07
  • @Evk Because if it's being calculated, it changes the timings so that the proportional difference in time between the two calls is a lot lower – Matthew Watson Jan 16 '23 at 10:08
  • 1
    Also I suspect that you are running this in Debug, try Release configuration with optimizations. Because I suspect it can be heavily optimized – Evk Jan 16 '23 at 10:08

1 Answers1

4

When using a value of 0.5 for time, the call to Math.Sin() ends up calling Math.Sin(0) - and I suspect that the implementation of Math.Sin() has an optimisation for that case that just returns 0.

The value passed to Math.Cos(), however, will be ~1.5707963705062866, and it's likely that this will NOT have a similar optimisation.

The fact that your time value was const also means that the compiler can pre-calculate the values passed to Math.Sin() and Math.Cos() which means that it doesn't matter if the calculation for Math.Sin() contains more operations, since they will not be performed at run-time.

Matthew Watson
  • 104,400
  • 10
  • 158
  • 276