I edited a post of mine and posted it to my web site that I think many around here might find useful. It concerns optimizing stuff for Unity. I got physics computational code to run about 1000% faster in my boat game this way. If you’re doing anything with heavy math or seem to be pushing the boundaries of Unity’s physics, you might find this useful.
Do you have test numbers to show the improvements this made?
I’m pretty surprised that Mono isn’t inlining calls to basic math operations.
There was a Unite video that showed the same thing a while back. There performance increase was similar. Google is currently hiding the video from me, but if I track it down I’ll post it.
At the bottom of one of the source code plots are a couple numbers for one function that gained about 10 times speed. Maybe it would be a good idea for me to post more numbers, or rewrite the article with some speed tests and results?
Inline functions: As far as I know this isn’t something that you can do in C#. I can’t imagine how it would be possible for Unity to go through and somehow convert all that on its own. I don’t know where one would even begin to do something like that.
Inlining functions isn’t something that I’d expect a programmer to do. It’s something that C/C++ compilers do which I assumed C# would also just do transparently behind the scenes. What I didn’t consider prior to my last post, however, is that in Unity our scripts are compiled to a DLL and the API functions we call are already compiled elsewhere. You can’t inline across that.
For what it’s worth, my understanding is that inlining is relatively straightforward to do at compile time under the right circumstances, because all it has to do is substitute a call for the actual contents of the function, exactly like you were doing in your examples.
Interesting, thanks. I didn’t know that. So basically even if C# did inline functions, this wouldn’t work in Unity because of the separate DLL’s? Makes sense.
Most of my years programming (VRC, VRC Pro) were with PowerBasic, similar to C but with Basic keywords. At the time there was no OOP at all. That language didn’t have inline functions either, but it had function macros which from a programmer’s perspective are pretty much the same thing. It’s just a text replacement done by the compiler which eliminates the actual function call. I used it a little bit, but it was somewhat tricky for me. With math heavy code like VRC’s physics engine which I wrote, I couldn’t imagine trying to do some function macro every time I add a couple of vectors together. So I did pretty much what you see in the code in my link, hand writing every single dot product, matrix multiplication, and everything. In the later years I started using function macros at least for the matrix multiplications, but that was one of few exceptions.
When I first started using Unity, I figured at least the overloaded stuff like adding two vectors together would internally do an SSE instruction or similar so the additions are done on all three components simultaneously. I’ve got a friend that also wrote a physics engine for his car racing simulator, and he hand coded ASM to do just that. Needless to say he got some huge performance improvements. That was all C++ where he used inline functions for things. I could be mistaken, but I think this was quite some time ago when I think you had to write them explicitly that way in order to be inline functions.
Anyway, what’s amusing to me is I’m finding more and more that my C# code in Unity is looking more and more like my old PowerBasic code. Go figure. ![]()
To me, the idea of actually writing out the same code dozens or hundreds of time is terrible. I wouldn’t do it if I could avoid it at all. For starters, it both increases the likelihood of a typo-induced bug at the same time as making them far harder to find. Yuck.
On the topic of inlining, I just did a little reading and it sounds like the C# compiler does indeed do it. It’s completely transparent, because the compiler does it based on its own rules. (Which I’m told is more or less true in C++ as well these days - there’s a related keyword, but it’s used as a “hint” at best.) There’s an attribute to prevent it, and in recent versions of .NET there’s an attribute to hint that a method should be “inlined if possible”.
So… I do have another test to suggest. Make your own math library - so that it’s compiled along with your own script code - and, rather than calling Unity’s functions, call your own. Do a speed test that vs. manually inlined code vs. Unity’s own calls. I’m not sure about Mono’s inlining criteria, so it could be worth trying with a few different methods. It would also be worth testing per-platform, since Mono may JIT differently on different CPUs.
Interesting on the inlining. I was must have been misinformed. I don’t recall my C# book mentioning it, but perhaps I just missed it.
I can understand that from a design view. It’s not pretty, but realistically, VRC wouldn’t have been possible without doing that. Neither would the boat sim I’m doing now. I don’t see trading away 10 or so times the performance in critical areas just for the sake of pretty looking code. That’s just me.
That kind of misses the whole point of the article though, doesn’ t it? The idea is to avoid calling functions to do simple math operations, whether they’re Unity’s or Microsoft’s or my own. My function for adding two vectors together would be the same as Unity’s or anyone else’s, probably. Did I miss something?
Just spent a couple minutes looking for C# inlining. I’m not finding anything. Do you have any links? From here:
This is from 2009 though. Has something in C# changed in this area since then? What I’m talking about doing is something like the first example which he says has no equivalent in C#.
There’s no equivalent keyword. The compiler does or does not do this for us behind the scenes at its whim. The first attribute on this page is about aggressive inlining. This page has an attribute that ensures inlining of a method does not happen. It’s not mentioned much in the docs because, like I said, it’s not a language feature so much as it’s a compiler feature. We’re not meant to have to think about it.
The idea is that you write your method just like you would any other method. At compile time, if they are compiled at the same time, the compiler may choose to inline the called method into the calling method based on some set of criteria. People are suggesting that the criteria includes the called method fitting into 32 bytes of IL, but that’s all I found in my short search, and .NET and Mono may be different in any case, especially across platforms. Hence suggesting a test.
It doesn’t miss my point. ![]()
If we’re only missing out on inlining because of the interop between our game DLL and the engine, then having a math library compiled along with our game DLL potentially gets the best of both worlds - we’ll get some or all of the performance boost from inlining at a much lower productivity overhead (importing and remembering to use a 3rd party math library, rather than hand-writing every math function every time we use it).
I must be missing something then, perhaps my knowledge isn’t quite there. Just to clarify, what I’m talking about is this:
Vector3 firstTerm=whatever;
Vector3 secondTerm=whatever;
Vector3 answer;
answer=firstTerm+secondTerm;
…not being nearly as fast as this:
Vector3 firstTerm=whatever;
Vector3 secondTerm=whatever;
Vector3 answer;
answer.x=firstTerm.x+secondTerm.x;
answer.y=firstTerm.y+secondTerm.y;
answer.z=firstTerm.z+secondTerm.z;
I’d have to double check to be sure, but I seem to remember the second version being several times faster than the first. So what I’m talking about doing is inlining an add function or overload in place of the Vector3 one in Unity. Can you demonstrate how to do that using the links you’ve provided?
I don’t understand how my replacing the Vector3 class with my own version of that would make any difference. An addition overload/function is still being called, no? The idea of inlining it is to remove the function call completely which makes it the same as the second code block. You’re saying this can be inlined, but I don’t understand how to go about that. Are you saying that doing it in a separate library would change that, that the JIT compiler (or whatever it is, I just read that for the first time 2 seconds ago) would then automatically start inlining it and that speed difference might disappear?
The first is still making a method call. As Vector3 is not a primitive data type, the “+” operator is implemented as a method, so using it still incurs a method call - essentially equivalent to “Vector3 Vector3.Add(Vector3 lhs, Vector3 rhs)”. On the other hand, float is a primitive data type, and in the second example you’re just adding three floats - no method call.
No. I can’t demonstrate how to do it because it’s not something programmers do.
Unlike certain older languages there is no way to manually tell C# to inline a method. As I said before, it’s something done by the compiler, if and when it chooses to do so.
I don’t know. That’s what I’m curious about. Sure the code for an additional function will be there. The question is whether Unity’s Mono will inline it for us.
I don’t think I can explain it more than I already have without getting into a lengthy explanation of how compilers work, which I’d rather not do since I’m not an expert on that.
All I’m saying is that, since it’s a compiler thing, and compilers behave differently in different circumstances, it’d be worth seeing what the Mono compiler in Unity does in those three different cases and whether or not it has performance implications. I’m not saying that it will, I’m just curious to know if it does. I only suggested that you do the testing because you’ve already been doing a bunch, so it seemed to be you’d be interested. I’m happy enough to do it myself later on, since this is arising from my own curiosity.
There is an intermediate way to test this quickly, without going to the effort of finding or building a different math library. Simply tack the addition method on to the bottom of one of your classes that you previously benchmarked, and see if there is a performance difference there.
I would have guessed it would be opposite because the Vector3 comps were probably using c++, also, like someone said, this kind of thing is supposed to be done at a compiler level. That’s the whole idea. It’s like making us into compilers.
Thanks for the clarification. I think I understand what you’re saying now.
That’s an interesting idea. So what you’re suggesting is I could test this by making my own Vector3Todd class, put a dot product or add method in it and compare speed with Vector3 to see if Mono inlines the Vector3Todd calls on its own? Sounds easy enough. I’ve done a little bit more reading since then about the automatic inlining stuff. This was something I knew nothing about until yesterday. Thanks for the education.
I’ll be really surprised if there’s any difference (why would it inline my class function calls but not any others?), but it’s worth a try. I don’t think I’ve ever seen a case where doing a method call to do anything was as fast as skipping the call, but there’s only one way to know for sure. That’s what inlining is supposed to do, after all.
If it does indeed work, I’m wondering if it might have to unroll the loops in order to work. Like maybe it might work for a couple lines, but putting it in a loop that’s 10,000 iterations might make it stop because it’d have to unroll it all? I don’t know very much about this, maybe I have totally the wrong idea there.
Thanks everyone the suggestions. I’ll try it and report back what I find. It’s a bummer that we can’t do inline assembly in C# (or can we?) so we could take advantage of SSE instructions and so forth to do vector computations in parallel. I suppose a dll could be made to do that with functions, although I have no idea if overrides could be done that way so you could keep the same syntax. I’d prefer to use + and - over add() and subtract().
I wouldn’t think so. This is C# code, I’d think doing what you’re talking about would mean the compiler would be translating parts of it into C++ and leaving the rest in C#. I can’t imagine that’s the case.
Ok, I just tried the tests and the results were what I was expecting. It didn’t inline my version of the class method calls.
There are five tests here with addition and dot products including results from the profiler. This is all done in FixedUpdate with enough loops to fill up about 80% of the CPU time in an empty scene, but not enough to take longer than the time between FixedUpdate() calls.
I wanted to try operator overloading with an addition op in my Vector3Alternate class, but couldn’t figure out how to do it and didn’t want to spend all night on this. Given these results I wouldn’t expect it to be much different anyway, unrolling the computations and skipping the method calls was still almost 6 times faster (600%) for Dot product and 20 times faster (2000%) for additions on Vector3s.
TestVector3Dot() - This calls the Vector3.Dot method.
TestVector3AlternateDot() - This is my version of a simple Vector3 class that has its own Dot method. This ran slightly faster (3% or so) than the Vector3 version. My guess would be because my x,y,z are public floats rather than properties accessed through getters/setters. If it were being inlined it should be closer to 600% faster, I’d think.
TestUnrolledDot() - Here we skip the method call on the Vector3.Dot, unroll it manually, and get a ton more speed. In this test it was in the neighborhood of 500-600% faster.
TestVector3Addition() - Addition operator overload on Vector3 class (adding two vectors without breaking them into components).
TestAdditionUnrolled() - Adding two Vector3s by unrolling it and skipping the operator overload (breaking them into components). This was a whopping 2000% faster than using the overload.
Results from one sample in the profiler, eyeballed to be about an average value:
TestVector3Dot() - 2.66ms
TestVectorAlternateDot() - 2.59ms
TestDotUnrolled() - 0.46 ms
TestVector3Addition() - 5.28 ms
TestAdditionUnrolled() - 0.27 ms (WOW)
using UnityEngine;
using System.Collections;
public class Vector3Alternate : MonoBehaviour
{
public float x;
public float y;
public float z;
public static float Dot(Vector3Alternate arg1, Vector3Alternate arg2)
{
return arg1.x * arg2.x + arg1.y * arg2.y + arg1.z * arg2.z;
}
}
public class TestMath : MonoBehaviour
{
private Vector3[] vect = new Vector3[3];
private Vector3Alternate[] vectAlternate = new Vector3Alternate[3];
const int numberOfLoops = 35000;
// Use this for initialization
void Start()
{
vect[0].x = 1;
vect[0].y = 1;
vect[0].z = 1;
vect[1].x = 2;
vect[1].y = 2;
vect[1].z = 2;
vect[2].x = 3;
vect[2].y = 3;
vect[2].z = 3;
vectAlternate[0] = new Vector3Alternate(); //Why do I have to do this here but not in a Vector3? Is something missing in Vector3Alternate class?
vectAlternate[0].x = 1;
vectAlternate[0].y = 1;
vectAlternate[0].z = 1;
vectAlternate[1] = new Vector3Alternate(); //Why do I have to do this here but not in a Vector3? Is something missing in Vector3Alternate class?
vectAlternate[1].x = 2;
vectAlternate[1].y = 2;
vectAlternate[1].z = 2;
vectAlternate[2] = new Vector3Alternate(); //Why do I have to do this here but not in a Vector3? Is something missing in Vector3Alternate class?
vectAlternate[2].x = 3;
vectAlternate[2].y = 3;
vectAlternate[2].z = 3;
}
void TestVector3Dot()
{
float dot;
for (int i = 0; i < numberOfLoops; i++)
{
dot = Vector3.Dot(vect[0], vect[1]);
}
}
void TestVector3AlternateDot()
{
float dot;
for (int i = 0; i < numberOfLoops; i++)
{
dot = Vector3Alternate.Dot(vectAlternate[0], vectAlternate[1]);
}
}
void TestUnrolledDot()
{
float dot;
for (int i = 0; i < numberOfLoops; i++)
{
dot = vect[0].x * vect[1].x +
vect[0].y * vect[1].y +
vect[0].z * vect[1].z;
}
}
void TestVector3Addition()
{
for (int i = 0; i < numberOfLoops; i++)
{
vect[2] = vect[0] + vect[1];
}
}
void TestAdditionUnrolled()
{
for (int i = 0; i < numberOfLoops; i++)
{
vect[2].x = vect[0].x + vect[1].x;
vect[2].y = vect[0].y + vect[1].y;
vect[2].z = vect[0].z + vect[1].z;
}
}
// Update is called once per frame
void FixedUpdate()
{
TestVector3Dot();
TestVector3AlternateDot();
TestUnrolledDot();
TestVector3Addition();
TestAdditionUnrolled();
}
}
So yeah, call unrolling everything terrible practice if you want, but to me it’s worth doing to get a nearly 2000% speed improvement in addition operations and over 500% in dot products. These are really massive gains, too big for me to ignore or trade away for the sake of prettier looking code. Like I wrote in my article, the boat simulator I’m doing simply wouldn’t be possible on the CPU at those resolutions without going through the trouble to do this kind of thing. I don’t think VRC Pro would have worked either.
It’s unfortunate, but it would appear there is no inlining going on with any of this.
Unrolling everything is bad practice.
Bad practice in hot code is good game development.
It’s not necessarily bad practice. I’d definitely call it terrible code, but that’s based on what I think it’d be like to work with, not what I think about the person who wrote it. I’ve written some shocking code in my time, but being a pragmatic coder sometimes means writing icky code to get the job done in less-than-ideal circumstances. A compiler not inlining for you where you need the performance would definitely fall in that bucket.
I’m pretty disappointed at the lack of inlining. I’d answer some of your questions above, Todd, but am on a tablet right now and it’s not my fastest/favourite typing experience. Might come back later if I remember.
It’s not a unity physics article, it’s a function overhead article.
Haha, ok, I can live with that. ![]()
Yes, that’s true of course. I’ll probably keep the article that way because I had Unity developers in mind when writing it. It should apply elsewhere too of course.
One more test, this time a cross product with a dot. What I see a lot of is people packing lots of cross and dot products into one line like this:
void TestVector3CrossAndDot()
{
float dot;
for (int i = 0; i < numberOfLoops; i++)
{
dot = Vector3.Dot(Vector3.Cross(vect[0], vect[1]), vect[2]);
}
}
void TestVector3CrossAndDotUnrolled()
{
float dot;
Vector3 cross;
for (int i = 0; i < numberOfLoops; i++)
{
cross.x = vect[0].y * vect[1].z - vect[0].z * vect[1].y;
cross.y = vect[0].z * vect[1].x - vect[0].x * vect[1].z;
cross.z = vect[0].x * vect[1].y - vect[0].y * vect[1].x;
dot = cross.x * vect[2].x +
cross.y * vect[2].y +
cross.z * vect[2].z;
}
}
The unrolled version ran somewhere around 1100% to 1240% faster, so it appears likely that this just gets worse as you pack more stuff into a single line. Of course to be really thorough I should just do a Cross by itself, but I think the general idea is good anyway. There’s no inlining happening anywhere.
What I see a lot of in the forums here are people advising folks to revisit their basic algorithms when they’re getting into their big bottlenecks. This is good of course, but what I don’t see anyone doing is telling them to unroll their math and quit packing a million things into a giant line filled with cross and dot products and “new Vector3” statements with math overloads and so on. As though one line of code runs faster than two or ten. There is often more to be gained in simply unrolling things to be more CPU friendly than there is in rethinking the algorithms. Of course it’s best to do both, but this whole area seems missing from the conversations here which I’ve always found rather mystifying.