Search Unity

Faster Math

Discussion in 'Editor & General Support' started by Brian-Kehrer, May 26, 2009.

  1. Brian-Kehrer

    Brian-Kehrer

    Joined:
    Nov 7, 2006
    Posts:
    411
    I've noticed that it's possible to outpace the built-in math functions quite simply. I'm assuming the scripting is slower than the functions in the engine. Which leads me to wonder if some of these could get a little optimization.

    Ordinarily this sort of thing doesn't matter in comparison to other game mechanics, but since this is almost the basic math - it would be nice if it were super optimized - since in many cases, thousands of math operations per frame are a reality.

    I guess the Mathf stuff just redirects to the system.math libraries, but still - things like lerp are easily beaten with simple scripting.

    Code (csharp):
    1.  
    2. public static Vector3 QuickLerp3(Vector3 one, Vector3 two, float t) {
    3.         t = Mathf.Clamp01(t); //also can rewrite clamp to pass the value type by reference, if you want to squeeze performance out
    4.         return new Vector3(
    5.                 one.x + (two.x - one.x)*t,
    6.                 one.y + (two.y - one.y)*t,
    7.                 one.z + (two.z - one.z)*t
    8.             );
    9.     }
    10.  
    I've also rewritten absolute value, squared, and the clamps, all of which are much faster. I haven't looked at the matrix operations, I'm assuming those have been optimized internally.
    Furthermore the absolute value function could be optimized to simply remove the sign bit, it would be cool if some of this were done in the engine itself - as it would likely run even faster.
     
  2. jcarpay

    jcarpay

    Joined:
    Aug 15, 2008
    Posts:
    561
    Interesting, what is the actual speed difference?
     
  3. andeeeee

    andeeeee

    Joined:
    Jul 19, 2005
    Posts:
    8,768
    I must admit, I've always wondered if the Mathf routines just call the System routines and cast the parameters and results to float. It's a missed opportunity for optimisation if they do. The transcendental functions need to be calculated to more significant figures for doubles and this means more loop iterations and more CPU time. All this time is wasted if the result is rounded off to be converted to float!
     
    dCalle likes this.
  4. Brian-Kehrer

    Brian-Kehrer

    Joined:
    Nov 7, 2006
    Posts:
    411
    I did the ref function clamp as well - I measured a speed difference of 40% last night over a million iterations or so. The complete code looks like this:

    Code (csharp):
    1.  
    2. public static void RefClamp01(ref float number){
    3.         if (number > 1){
    4.             number = 1;
    5.         }  
    6.         else if(number <0){
    7.             number = 0;
    8.         }
    9.     }
    10.     public static Vector3 QuickLerp3(Vector3 one, Vector3 two, float t){
    11.         RefClamp01(ref t);
    12.        
    13.         return new Vector3(
    14.                 one.x + (two.x - one.x)*t,
    15.                 one.y + (two.y - one.y)*t,
    16.                 one.z + (two.z - one.z)*t
    17.             );
    18.     }
    19.  
    Here is a test case - depending on how you stress it you may notice more or less speed difference. My clamp in particular is much faster for values of t > 1 or less than 0 in Lerp.

    Anyway, I'm wasting a lot of cycles doing other stuff, but I wrote this as a simple test case, and it still is a lot faster. I'm getting 0.369 seconds for A vs 0.305 seconds for B.

    Try it yourself...

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class Iterator : MonoBehaviour {
    6.  
    7.     // Use this for initialization
    8.     void Start () {
    9.         Iterate(1000000);
    10.     }
    11.    
    12.     void Iterate(int iter){
    13.         float xy = Time.realtimeSinceStartup;
    14.             for(int i=0; i<iter;i++){
    15.                
    16.                 TestA();
    17.                
    18.             }
    19.         float endA = Time.realtimeSinceStartup-xy;
    20.         float yz = Time.realtimeSinceStartup;
    21.             for(int i=0; i<iter;i++){
    22.                
    23.                 TestB();
    24.                
    25.             }
    26.         float endB = Time.realtimeSinceStartup-yz;
    27.         Debug.Log(endA + " : " + endB);
    28.     }
    29.    
    30.     void TestA(){
    31.         Vector3 test1 = new Vector3(Random.value,Random.value,Random.value);
    32.         Vector3 test2 = new Vector3(Random.value,Random.value,Random.value);
    33.         float blah = Random.value*2;
    34.         Vector3 result = Vector3.Lerp(test1, test2, blah);
    35.        
    36.     }
    37.     void TestB(){
    38.         Vector3 test1 = new Vector3(Random.value,Random.value,Random.value);
    39.         Vector3 test2 = new Vector3(Random.value,Random.value,Random.value);
    40.         float blah = Random.value*2;
    41.         Vector3 result = MathLib.QuickLerp3(test1, test2, blah);
    42.     }
    43. }
    44.  
     
  5. jonas-echterhoff

    jonas-echterhoff

    Unity Technologies

    Joined:
    Aug 18, 2005
    Posts:
    1,666
    They don't! converting to double and back would indeed be very slow, but it's not what we're doing. Still, interesting observation. Can you give any numbers by how far your custom math is faster then ours?

    edit: too slow, you just did. Interesting. I will investigate this further..
     
    dCalle likes this.
  6. Brian-Kehrer

    Brian-Kehrer

    Joined:
    Nov 7, 2006
    Posts:
    411
    Thanks for taking a look.

    I also discussed with a coworker - I can also match the speed of many of the functions - such as Vector3.Dot and Vector3.Cross.

    My expectation was that since this is relatively simple math I wouldn't be able to beat the engine.

    Either these aren't being called in C++, Mono is just as fast, or there is so much overhead associated with calling a C++ function the math is insignificant compared to the function call.

    Like I said before, I realize ordinarily this sort of optimization is trivial, but in the case of these basic math operations - squeezing some extra performance out would help everyone.
     
  7. n0mad

    n0mad

    Joined:
    Jan 27, 2009
    Posts:
    3,732
    bump.

    Any news on internal optimizations ?
     
  8. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
  9. n0mad

    n0mad

    Joined:
    Jan 27, 2009
    Posts:
    3,732
    You're great, thanks ^^
     
  10. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    As an update, while Unity 2.6 did in fact speed up those Lerp functions a reasonable amount, it turns out it's still faster to write your own functions. So I've added SpeedLerp to the wiki, which has various replacements that range from 1.4X faster to 3.5X faster than using built-in Mathf functions, based on simple benchmarks.

    (If anyone was wondering how exactly my latest version of Fractscape got 2X faster for texture calculations and 4.5X faster for terrain calculations, that's a pretty big reason why. Among other things.)

    --Eric
     
  11. Daniel_Brauer

    Daniel_Brauer

    Unity Technologies

    Joined:
    Aug 11, 2006
    Posts:
    3,355
    Speaking of faster, I was looking through the disassembled SmoothStep function because it seems that there are a lot of ways to evaluate it. Unity uses

    Code (csharp):
    1. public static float SmoothStep(float from, float to, float t) {
    2.     t = Clamp01(t);
    3.     t = (((02f * t) * t) + ((3f *t) *t);
    4.     return ((to * t) + (from * (1f - t)));
    5. }
    which is only about 80% as fast as:

    Code (csharp):
    1. static float MySmoothstep(float a, float b, float t) {
    2.     // saturate t to 0..1 range
    3.     t = Mathf.Clamp01(t);
    4.     // Evaluate polynomial
    5.     return a + (t*t*(3-2*t))*(b - a);
    6. }
    But then who uses SmoothStep enough for it to matter?

    Note: I got Unity's code from the .NET reflector, so who knows what the original code looked like.
     
  12. Dreamora

    Dreamora

    Joined:
    Apr 5, 2008
    Posts:
    26,601
    Guess you forgot something in the original code, like a *t? otherwise yours would be wrong and the seperation into the 2 terms in the original one wouldn't make any sense :)

    And it makes sense that yours is faster, less multiplications to execute :) (original one is 6 mul, 3 sum while the new one is 4 mul, 2 sum)
     
  13. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    Me; it's very useful. Anyway, it's faster to return a if t < 0 or b if t > 1, instead of clamping it and then evaluating the function...seems kinda like a waste of CPU cycles when you already know the result in those cases. Unfortunately your routine doesn't seem to work or else I'd steal it if it would make SmoothStep faster. ;)

    --Eric
     
  14. prime31

    prime31

    Joined:
    Oct 9, 2008
    Posts:
    6,426
    I just found this thread due to the bumping and was curious so I ran UnityEngine.dll through Reflector.NET and contrary to what jonas echterhoff said a ways back a large portion of Mathf is just redirects to System.Math. Here is one example (there are a ton more in there):

    Code (csharp):
    1. public static float Round(float f)
    2. {
    3.     return (float) Math.Round( (double) f);
    4. }
    If this is correct then there could be a pretty big performance gain from a rewrite of the Mathf struct especially on the iPhone.
     
  15. andeeeee

    andeeeee

    Joined:
    Jul 19, 2005
    Posts:
    8,768
    Jonas may have been referring to the transcendental functions (trig functions, sqrt, etc). There would be a much greater performance hit for these than with things like Round.
     
  16. prime31

    prime31

    Joined:
    Oct 9, 2008
    Posts:
    6,426
    Round was just the one I was looking at when I copy/pasted. Almost every function in there is just a cast to double cast back to float.
     
  17. smarcus

    smarcus

    Joined:
    Sep 18, 2007
    Posts:
    113
    Is there any downside to using the MathS functions, Eric? Apart from the fact that the performance boost might not be huge? Basically all I do in my Update() calls is Vector3 lerping (with occasional slerping), so it'd be nice to get back even 2 or 3% of that processing time.

    I guess I'm nostly I'm curious to know why the home-brewed math functions are still faster than the builtins.
     
  18. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    It makes your executable a few bytes larger. :)

    --Eric
     
  19. smarcus

    smarcus

    Joined:
    Sep 18, 2007
    Posts:
    113
    ...my god.

    Sweet. Well, performance never hurts, so in it goes... I've been working exclusively with javascript... am I going to shoot myself in the foot using the .cs version of these routines? Should I just make a javascript partner?

    I know there can be issues using javascript and c# components at the same time but these are static functions so they should be safe, yes?
     
  20. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    No, I use Javascript 99% of the time. As the usage docs say: "Put the MathS script in the Scripts folder in Standard Assets; this way it can be called easily from Javascript and Boo." It's actually not about static functions, but rather script compilation order.

    The main reason it's in C# is that I was playing around with reference variables (which you can use in Javascript but not directly declare in functions) to see if there was any speed benefit...I had an overload where instead of doing "foo = Mathf.Lerp(0, 100, .5)", you'd do "Mathf.Lerp(foo, 0, 100, .5);" It turned out that didn't really help, but since MathS works fine from any language as-is, there's not much point rewriting it.

    --Eric
     
  21. smarcus

    smarcus

    Joined:
    Sep 18, 2007
    Posts:
    113
    Oops- I was doing last-minute editing on my post before I submitted it and neglected to paste something back in. (This is why one should never edit ones posts).

    It should have said:

     
  22. Quietus2

    Quietus2

    Joined:
    Mar 28, 2008
    Posts:
    2,058
    It's not special treatment per-se, it's about accounting for compilation order to make sure that the routines are available for your javascript.

    http://unity3d.com/support/documentation/ScriptReference/index.Script_compilation_28Advanced29.html

    If you want to use xyz function from Eric's class, it obviously has to be defined before you use it.

    You can see some examples of javascript->C# interoperability with the perlin noise generator on the resource section of the unity website.
     
  23. smarcus

    smarcus

    Joined:
    Sep 18, 2007
    Posts:
    113
    Good to know- thanks! I just made an otherwise unused folder named "Standard Assets" and now the myriad compilation errors are gone.
     
  24. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    Well, it's kind of special treatment...if you just make a folder called "Blah", that won't work.

    --Eric
     
  25. Orion

    Orion

    Joined:
    Mar 31, 2008
    Posts:
    261
    Hmm.. I noticed that using the System.Math functions instead of the Mathf functions is already a whole lot faster (including the casting from / to double, or using double right away).
    This seems to be especially the case for things like sin() or sqrt() and such.

    It would be nice if Unity could, for one, implement the math functions properly for floats (what else would be the point of having Mathf in the first place?), and then provide a built in library for functions that have to run lightning fast but don't nescessarily have to be exact.
    E.g. Sin() could be implemented with a lookup table that is precised to 1/10th of a degree (I did that for my own code and the performance gain is immense, while the precision loss is not noticable in most cases).

    I was quite surprised when I figured out that my code was just slow because I used rudimentary functions of Unity, like Sin() or Distance() etc. For a game engine that's a no-no :roll:
     
  26. alph

    alph

    Joined:
    Jul 20, 2010
    Posts:
    89
    I found this thread quite interesting as I'm a sucker for optimization.

    Is there any news from Unity developers on this issue? It seems to me extremely wrong that custom made math functions should be faster than the internal Mathf functions.
     
  27. Noisecrime

    Noisecrime

    Joined:
    Apr 7, 2010
    Posts:
    2,054
    Just came across this thread after profiling some code and noticing that Mathf.Abs() simply calls Math.Abs() in Unity 3.4.
    Quite a revelation as in my case I was using it to try and test for denormal numbers within a heavy loop doing image processing, resulting in over 600k calls per frame. The double call nature really hammered performance.

    Granted that was a specialized case, but still its something i'll keep in mind in the future. No point calling a Mathf function if all it does is call the corresponding Math function.
     
  28. MrBurns

    MrBurns

    Joined:
    Aug 16, 2011
    Posts:
    378
    Where we have two issues:

    1) Hail the thread necros ;), for no real reason...
    2) Profiling ^^. How did you profile? With Unity profiler? In either case, what shall "really hammered performance" mean? The processor simply doesn't care about double calls as long as they are unconditionally and have no profiling code in between ;), which they probably had in your case. Additionally you were probably using the debug version, which doesn't inline double calls...
     
  29. Noisecrime

    Noisecrime

    Joined:
    Apr 7, 2010
    Posts:
    2,054
    Other than it being of vital importance to those who are unaware of the situation and have heavy usage of Unity Mathf functions, you mean? ;)

    Along with the post above mine asking if there were any plans to address the situation, which is much better here than starting a duplicate new topic.

    Granted doing a deep profile in Unity didn't help its cause, but measuring the results (timing directly around the function) without the profiler revealed a similar situation;

    No ABS in function = 80 fps
    System Math.Abs = 64 fps
    Unity Mathf.Abs = 54 fps

    So whilst adding a call to Abs is costly regardless (to be expected), the drop of 10 fps is considerable considering you'd expect it to be the same performance as calling system Math.

    Obviously this is still a somewhat special case, Abs is being called hundreds of thousands times a frame, but then its precisely these situations where it would be vital to know that calling Unity Mathf is going to slow down.
     
    Last edited: Sep 23, 2011
  30. stfx

    stfx

    Joined:
    Jun 15, 2011
    Posts:
    7
    I know necroing, but this might be interesting:

    Unitys Min and Max function use >= respectively <= which is a small performance decrease over > and <. When viewing in a Reflector it looks like this:

    Code (csharp):
    1. public static float Min(float a, float b)
    2. {
    3.     return ((a >= b) ? b : a);
    4. }
    But the following which works the same is obviously slightly faster:

    Code (csharp):
    1. public static float Min(float a, float b)
    2. {
    3.     return (a < b) ? a : b;
    4. }
    5.  
    Also as Noisecrime mentioned Mathf has some functions which are a kinda pointless wrapper around some .net Math functions. Like:

    Code (csharp):
    1. public static int FloorToInt(float f)
    2. {
    3.     return (int) Math.Floor((double) f);
    4. }
     
  31. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    There isn't any performance difference between >= and >. (Or <= and <.)

    --Eric
     
  32. stfx

    stfx

    Joined:
    Jun 15, 2011
    Posts:
    7
    My bad, thanks for clarifying that