Faster Math

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.

public static Vector3 QuickLerp3(Vector3 one, Vector3 two, float t) {
        t = Mathf.Clamp01(t); //also can rewrite clamp to pass the value type by reference, if you want to squeeze performance out
        return new Vector3(
                one.x + (two.x - one.x)*t,
                one.y + (two.y - one.y)*t,
                one.z + (two.z - one.z)*t
            );
    }

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.

Interesting, what is the actual speed difference?

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!

1 Like

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:

public static void RefClamp01(ref float number){
		if (number > 1){
			number = 1;
		}	
		else if(number <0){
			number = 0;
		}
	}
	public static Vector3 QuickLerp3(Vector3 one, Vector3 two, float t){
		RefClamp01(ref t);
		
		return new Vector3(
                one.x + (two.x - one.x)*t,
                one.y + (two.y - one.y)*t,
                one.z + (two.z - one.z)*t
            );
	}

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…

using UnityEngine;
using System.Collections;

public class Iterator : MonoBehaviour {

	// Use this for initialization
	void Start () {
		Iterate(1000000);
	}
	
	void Iterate(int iter){
		float xy = Time.realtimeSinceStartup;
			for(int i=0; i<iter;i++){
				
				TestA();
				
			}
		float endA = Time.realtimeSinceStartup-xy;
		float yz = Time.realtimeSinceStartup;
			for(int i=0; i<iter;i++){
				
				TestB();
				
			}
		float endB = Time.realtimeSinceStartup-yz;
		Debug.Log(endA + " : " + endB);
	}
	
	void TestA(){
		Vector3 test1 = new Vector3(Random.value,Random.value,Random.value);
		Vector3 test2 = new Vector3(Random.value,Random.value,Random.value);
		float blah = Random.value*2;
		Vector3 result = Vector3.Lerp(test1, test2, blah);
		
	}
	void TestB(){
		Vector3 test1 = new Vector3(Random.value,Random.value,Random.value);
		Vector3 test2 = new Vector3(Random.value,Random.value,Random.value);
		float blah = Random.value*2;
		Vector3 result = MathLib.QuickLerp3(test1, test2, blah);
	}
}

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…

1 Like

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.

bump.

Any news on internal optimizations ?

http://unity3d.com/unity/whats-new/unity-2.6

–Eric

You’re great, thanks ^^

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

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

public static float SmoothStep(float from, float to, float t) {
	t = Clamp01(t);
	t = (((02f * t) * t) + ((3f *t) *t);
	return ((to * t) + (from * (1f - t)));
}

which is only about 80% as fast as:

static float MySmoothstep(float a, float b, float t) {
	// saturate t to 0..1 range
	t = Mathf.Clamp01(t);
	// Evaluate polynomial
	return a + (t*t*(3-2*t))*(b - a);
}

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.

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 :slight_smile:

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

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. :wink:

–Eric

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):

public static float Round(float f)
{
    return (float) Math.Round( (double) f);
}

If this is correct then there could be a pretty big performance gain from a rewrite of the Mathf struct especially on the iPhone.

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.

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.

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.

It makes your executable a few bytes larger. :slight_smile:

–Eric

…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?

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