Unity vs. Floating Point

ibobev1 pts0 comments

Unity vs floating point · Aras' website

A tweet by @VehiclePhysics<br>sparked my interest. It basically says:

For most math functions (Sqrt, Sin, Cos, Log, Pow…), prefer System.MathF<br>over UnityEngine.Mathf. Unity&rsquo;s Mathf casts to double, calls the double version,<br>then converts back to float. System.MathF calls the float-native implementations<br>directly. Less work, same result.

This advice is basically correct! But turns out, things are slightly more<br>complicated.

Hidden double precision in Unity

The advice above applies to all UnityEngine.Mathf methods that deal with<br>trigonometry (Sin, Cos, Tan, Asin, Acos, Atan, Atan2),<br>exponentials (Sqrt, Pow, Exp, Log, Log10),<br>rounding (Ceil, Floor, Round, CeilToInt, FloorToInt, RoundToInt),<br>comparisons (Min, Max, Clamp, Clamp01) and others (Sign, SmoothStep,<br>Gamma, Approximately, InverseLerp). About the only function it does not apply to<br>is Mathf.Abs.

But… why? Well, because C#/.NET originally did not have single-precision<br>methods for these sorts of math functions. The single precision System.MathF<br>was introduced in .NET Core 2.0 (year 2017).

Now, you might have expected that almost ten years later, maybe Unity would<br>have noticed this, and made them single precision? Alas, no. There could be potential<br>backwards compatibility issues preventing that (or maybe not! see below).

You also might have guessed that<br>Unity.Mathematics package,<br>which was introduced (year 2019) as part of the whole DOTS push, and is modeled to be very similar<br>to HLSL, would actually do single precision floating point for functions that look like<br>single precision floating point… and that would be wrong too; for all the trigonometric<br>and exponential functions like math.sqrt(float x) it routes that into the double<br>precision C# implementation. Why? I don&rsquo;t know.

But wait! There is way more double precision. The Mono C# runtime used in Unity<br>does all math in double precision, everywhere . Yes, this means there is a ton<br>of float⭤double conversions from in-memory representation to in-register representation,<br>all over the place. I have first noticed this back in 2018 when doing a<br>toy path tracer,<br>and then Miguel de Icaza did an explanatory blog post,<br>with plans outlined how to switch Mono to use actual floats for floats (yeah!).

&ldquo;In Mono, decades ago, we made the mistake of performing all 32-bit float computations as 64-bit floats while still storing the data in 32-bit locations.&rdquo;

Official Mono releases have switched to do that since then, but (I think) for backwards compatibility<br>reasons Unity never enabled that functionality and kept everything at double precision so far.

Note however that the above only applies to Mono. The other two C# language/runtime implementations used<br>across Unity today, IL2CPP and Burst, do not have the &ldquo;everything is actually double precision&rdquo;<br>behavior. It is weird that Unity would not switch their Mono version to match;<br>after all some of their main deployment platforms never use Mono (iOS, consoles, web)!

Let&rsquo;s look at a square root

The above is fairly abstract, so let&rsquo;s look at what actually happens with a very simple<br>loop that sums up a bunch of square roots:

const int N = 10000000;<br>public static float UnityMathf(float v)<br>for (int i = 0; i<br>In the Unity editor (6000.0.76, but rough timings are the same on 2022.3, 6000.3 and 6000.6 versions),<br>on Windows / Ryzen 5950X machine: UnityEngine.Mathf 282ms , System.MathF 186ms .<br>Whoa indeed, this is way faster!

But hey! Back in 2018<br>we already found that Unity&rsquo;s C# performance also very much depends on whether script debugging<br>is enabled or not. Back then it was called &ldquo;Editor Attaching&rdquo; under preferences; these days<br>it is this bad-contrast-in-light-theme Debug vs Release widget at lower right editor corner.<br>In Release mode, in-editor timings are: UnityEngine.Mathf 242ms , System.MathF 149ms .

More square roots in more C# variants

To get a more complete picture, let&rsquo;s also add a variant that uses the<br>&ldquo;new way of doing math&rdquo; in Unity, i.e. the<br>Unity.Mathematics package. And have timings for a player build that uses Mono, plus timings for an<br>IL2CPP scripting backend.<br>And while at it, also test performance of the same code under<br>Burst compiler.

Editor Debug<br>Editor Release<br>Player Mono<br>Player IL2CPP

Mathf<br>282<br>242<br>212<br>35

System.MathF<br>186<br>149<br>142<br>35

Mathematics<br>260<br>211<br>209<br>59

Burst Mathf<br>66<br>66<br>67<br>60

Burst Mathematics<br>35<br>34<br>34<br>34

And for a complete picture, the same loop, using System.MathF.Sqrt (C#) or sqrtf() (C++)<br>in non-Unity implementations / runtimes:

C# Mono 6.12<br>C# .NET 10<br>C++ /O2

System.MathF<br>130<br>37

sqrtf()

35

Summary of the above:

35 milliseconds to do this loop is &ldquo;as good as it can get&rdquo; on this machine, and that is achieved<br>by C++ & .NET, and within Unity by using Burst + Unity.Mathematics, or when using IL2CPP, with<br>either of Mathf.Sqrt or System.MathF.Sqrt. Under IL2CPP, there does seem to be<br>some...

mathf unity precision mono system double

Related Articles