Performance issues. How fast is C# in Unity compared to say, native C++ on a device?

I’m trying to do a Life simulation in Unity and I’ve run into some severe performance issues, among other problems.

Here are the relevant portions of my code, called in FixedUpdate:

// Compute the next life step:
void UpdateCellMatrix()
{
    int n; // Number of adjacent cells alive.
    int state;

    for (int j = 0; j < height; j++)
    {
        for (int i = 0; i < width; i++)
        {

            // Count living neighbors. Matrix wraps at borders to avoid a bunch of conditional jumps.

            n = cellMatrix[i, (j + 1) % height]; // top
            n += cellMatrix[(i + 1) % width, (j + 1) % height]; // top right
            n += cellMatrix[(i + 1) % width, j % height]; // right
            n += cellMatrix[(i + 1) % width, (height + j - 1) % height]; // bottom right
            n += cellMatrix[i % width, (height + j - 1) % height]; // bottom
            n += cellMatrix[(width + i - 1) % width, (height + j - 1) % height]; // bottom left
            n += cellMatrix[(width + i - 1) % width, j % height]; // left
            n += cellMatrix[(width + i - 1) % width, (j + 1) % height]; // top left

            state = cellMatrix[i, j]; // Get state of cell.

            if (state == 1) // If cell is alive...
            {
                if ((n < 2) || (n > 3)) { state = 0; } // If cell is alive and has less than 2 or more than 3 alive neighbors, kill it.
            }
            else // If cell is dead...
            {
                if (n == 3) { state = 1; } // If cell is dead and has exactly 3 living neighbors, kill it.
            }

            newMatrix[i, j] = state; // Record new cell state.

        }
    }

    swapMatrix = cellMatrix;
    cellMatrix = newMatrix; // Swap array pointers.
    newMatrix = swapMatrix;

}

// Draws the cell matrix to the texture.
void DrawCellMatrix()
{
    int index = 0; // ints in Unity are 32 bit.

    for (int j = 0; j < height; j++)
    {
        for (int i = 0; i < width; i++)
        {
            if (cellMatrix[i, j] == 0) { newColors[index] = Color.black; } else { newColors[index] = Color.white; }
            index++;
        }
    }

    tex.SetPixels32(newColors); // Second parameter is mip level?
    tex.Apply(); // Update texture on sprite.

}

I have already run the profiler and confirmed that 95% of the time the code is in the UpdateCellMatrix() function. I assume the texture update code is almost as fast as it can be made. I mean I can probably reduce those two loops to one, but I’m guessing the bottleneck there is updating the texture on the card not the speed of that loop.

I should mention that I am running this within the editor. I do not know if running within the editor the code will execute slower.

I also do not know if there is some sort of debug mode being enabled somewhere causing code execution to slow down. I am new to Unity.

I am however not new to programming, just C#, and I know using 2D arrays is bad for performance and having a bunch of modulos in there is also bad. I was just trying to get this up and running as a proof of concept that is easy to understand and could be easily modified before I went crazy with optimizations.

Still, even without optimizations this should not run this slow. It looks like its running at like 4fps. And a friend has run the same simulation on a a crappy old linux box in matlab of all things, and he’s getting 200fps.

So what’s going on here? Is C# interpreted in Unity? Is there some debugger enabled that’s slowing things down and which enables that profiler to work? (It’s slow whether or not I have the profiler window open.) If so, how do I disable that? Or is the code always slow within the editor? I’m going to build the project and check its speed next, but I have my doubts that is going to help.

What I really want to know is:

Is there any way I can speed this up beyond simply optimizing my code? Is something other than my crappy algorithm slowing this way down?

ADDENDUM:

I asked about this on Reddit and I got a bunch of irrelevant responses which missed the point so I want to clarify some things:

  1. I am not looking to improve the frame rate. I am looking to increase the speed of the simulation.

Slowing down the simulation to boost the frame rate misses the point. And co-routines will not help here.

(Well I mean, maybe if they run on a separate core they might? The code’s still super slow though. I don’t know much about how co-routines work.)

  1. I am aware the code is unoptimized. I am not looking for suggestions on how to move some of the calculations out of my loop.

I am looking for an explanation for why similar unoptimized code runs 50x faster in Matlab on a 10-15 year old Linux box.

The questions I need answered are:

  1. Is C# in Unity in the general vicinity of being as fast as compiled C++? Or is it interpreted at runtime like Javascript in a browser?

  2. Is there some debugger in Unity that slows things down that I can disable?

  3. Will my code run much slower when I am running it in the editor versus building it as a final project?

1 Like

So I just found out that if you build a release version the code runs way faster… They should really warn you about that!

At least you’re starting with a real performance problem. A lot of people want help with their imagined performance problems. :slight_smile:

You present 1 as a dichotomy, which it may be for you. Generally speaking, C# compiles to .NET or .NET-like output. On Unity, I think it’s Mono…maybe a custom flavor of that? Those intermediate languages are, as I’m sure you already know, compiled just in time.

So, for long-running algorithms (which I would expect a simulation of the game of life to be), the instructions end up being about as fast.

One thing that differs between a managed language and an un-managed language like C++, however, is all the stuff you don’t see. With C++, everything is traceable, if not explicit. Want something on the heap? Make it and, if you want it ever to leave the heap, you’ve got to make it go away, too.

That’s not the case with managed languages. .NET, specifically, does a bunch of weird stuff with memory. The address of an object in memory can (at least theoretically) move during its lifetime. I find this to be a mind-bending concept and don’t understand their reasons (mostly because I haven’t looked into them) but there it is. I don’t know if it’s that way with mono.

I can’t fathom what about your algorithm is taking so long. I’m skeptical that we’re actually looking at the true source of the problem. I don’t much trust the Unity profiler, which compounds my skepticism.

It looks fine to me. Have you tried something like running a couple hundred generations in a single frame? I don’t mean that as a suggested solution, just as a diagnostic step. If running a hundred generations (without rendering) in a single frame drops the frame rate by an approximate factor of 100, you can narrow down the problem…make absolutely certain that it’s in UpdateCellMatrix. If not, then you can try re-rendering 100 times in a frame and see if that’s really the problem.

You could also try moving this to Update instead of FixedUpdate…again, as a diagnostic step, rather than as a solution. If the problem is that FixedUpdate is running a lower number of frames per second than is possible, we can come up with a solution.

No C# is not as fast as C++. And this is regardless of Unity (technically unity speeds up C# in some instances, I’ll get to that).

C# is a JIT compiled language, like Java. C# is compiled into CIL (sort of like java bytecode). There is a runtime, .net runtime, or in the case of Unity the mono runtime… where at runtime the CIL is compiled into machine code on the fly.

Optimizations to the runtimes have made them slightly comparable to C++ compiled code, BUT it’s still technically slower and Unity uses a much older runtime that lacks many optimizations.

With that said, when you build and target a platform using IL2CPP, this converts the C# to C++ and then builds natively to that device and will run faster.

Yes, there is a debugger in the editor.

Yes

As for why your code may be running at such a low framerate… no idea as of yet. I have not reviewed the code.

If you could supply the example project in full so that I could easily run it, I would take a look. But I’m not going to rebuild it as is from your code snippet as I am on my laptop out of state and don’t want to.

Is this the exact same code? There must be something else, maybe matlab has some special system for these kind of simulations?

Also I wonder what is the number of width and height? do we talk about hundreds or millions of cells?

Well I am curious, maybe I give UpdateCellMatrix() a try on my own later at home :slight_smile:

Two things:

  1. Uploading a new, modified texture from the CPU to the GPU (texture.Apply()) is painfully slow, especially on mobile devices. You need to come up with a different way of drawing the game board for peak performance.

  2. If your frame rate drops, FixedUpdate will run more often with less visible results, which is exponentially compounding your issue. You could be drawing and updating the game board 10 times without visually seeing any of the results.

1 Like

Right, if you’re interested only in the speed of your simulation, you should disable the texture update, at least as a test to see how much difference that makes. It could be quite a bit.

How much faster? Did this completely solve your concern?

It’s not super fast, but its fast enough to do a 256x256 grid at what looks to be around 30fps and I’m now confident that the remaining slowness is most likely a result of my unoptimized code, the fact that I’m writing to a texture, and that I’m running the code in fixedupdate().

So I’d say it’s solved my concern. I know with some optimization I can get this to be at least 10x as fast and I should eventually be able to a 1024x1024 grid at a reasonable frame rate. On a PC at least. I’m concerned about what the performance will be like on a cellphone, but I’ll cross that bridge when I get to it.

It’s a fun problem. I’m sure the community can think of lots of optimization ideas… in fact you might even consider trying the PixelSurface asset in my sig, which can neatly avoid updating parts of the texture that aren’t changing (which tends to happen a lot in Life simulations once they settle down).

So, please keep us posted as it evolves, and if you feel like it, don’t be shy about asking for input!

When you get around to doing a few modifications, try this:

n = cellMatrix[i, (j + 1) % height]; // top
            n += cellMatrix[(i + 1) % width, (j + 1) % height]; // top right
            n += cellMatrix[(i + 1) % width, j % height]; // right
            n += cellMatrix[(i + 1) % width, (height + j - 1) % height]; // bottom right
            n += cellMatrix[i % width, (height + j - 1) % height]; // bottom
            n += cellMatrix[(width + i - 1) % width, (height + j - 1) % height]; // bottom left
            n += cellMatrix[(width + i - 1) % width, j % height]; // left
            n += cellMatrix[(width + i - 1) % width, (j + 1) % height]; // top left

First of all you should definitely save ((j+1) % height) into a local variable instead of calculating it again and again.
Same with all other expressions. (i+1)%width …
Then you should use “jagged” arrays instead of “square”, in case you don’t know what that means: Its simply using int[ ][ ] instead of int[,]. There is a performance difference between the two.

Maybe that also helps.
And as you have discovered already, there is a large difference between debug and release builds.
Most of that comes from the jit compiler though, not from the compiler flag. And how the jit compiler produces code is determined by the presence of a debugger.

1 Like

I don’t think we’re actually supposed to be brainstorming yet. :wink: But when we do, what the really high-end CA simulators do is divide the grid into regions, and keep track of which regions have any activity going on at all. Then you can entirely skip the regions where you know nothing is going to change.

The bookkeeping gets a lot more complicated, but it’s worth it for the massive speed-up you get in typical cases (though obviously it’s slightly worse in the worst case).

I’m not shy about asking for input, but I am a game programmer with over 20 years of experience on slow crappy PCs coding down to the metal, so I already knew a lot of the optimizations people were going to suggest and I was pretty sure the problem was something else but I’d just gotten it working before I went to bed and I asked in the morning before I finished exploring every avenue because I thought it might take a while to get answers. :slight_smile:

I’m glad to see the community is so helpful though!

Still even with my experience there’s a lot I don’t know about Unity and its a big API so I’m sure I will miss some optimization opportunities as I learn it, and I do have one question I can think of off the top of my head…

I was trying to use the raycasting functions to get the mouse cursor position within my texture on my sprite, but as of a few days ago they weren’t working and I haven’t had a chance to go back and look at it. I wanted to be able to click to change cells in my simulation.

Do you know if raycasting even works with sprites? I know they’re special in some ways in Unity, so for all I know it doesn’t work and it’s not telling me I’m doing something bad trying to make it work.

Here’s my code for that. Sorry about the mess, I was commenting everything out trying to get the texture drawing stuff to work.

//FixedUpdate is called at a fixed interval and is independent of frame rate. Put physics code here.
    void FixedUpdate()
    {

        Vector3 pos = Input.mousePosition; // Get mouse position
        //Debug.Log(pos);

        /*
        RaycastHit hit; // Create a RaycastHit struct called hit.
        //Camera _cam = Camera.mainCamera; // Get camera to use for raycasting
        Camera _cam = Camera.main; // Get camera to use for raycasting
        Ray ray = _cam.ScreenPointToRay(pos); // Define a ray to cast using mouse position on screen.
        Physics.Raycast(_cam.transform.position, ray.direction, out hit, 10000.0f); // Cast a ray from the camera's position in the direction defined by the ray, and return the result in the hit struct.
        Debug.DrawRay(ray.origin, ray.direction * 10, Color.yellow); // Draws a line on the screen that matches the ray cast for debugging.
        */

        //Color c;

        /*
        if (hit.collider) // If our ray hit something...
        {
            Debug.Log("hit!");

            Texture2D tex = (Texture2D)hit.collider.gameObject.GetComponent<Renderer>().material.mainTexture; // Get the texture of the object we hit.
      
            Vector2 pixelUV = hit.textureCoord; // Get texture coordinates of raycast hit.

            pixelUV.x *= tex.width; // Calculate XY texture coordinates from the UV coordinates returned by the raycast hit.
            pixelUV.y *= tex.height;

            tex.SetPixel((int)pixelUV.x, (int)pixelUV.y, Color.black); // Faster to use SetPixel32. Better yet use SetPixels32.

            tex.Apply(); // This applies the changes we made to the texture in RAM by copying them to texture memory.

            //c = tex.GetPixelBilinear(hit.textureCoord2.x, hit.textureCoord2.y); // Get interpolated color from texture using U,V coordinates.

        }
        */

        // *** OLD ***
        //Texture2D tex = (Texture2D)GetComponent<Renderer>().material.mainTexture;
        //Texture2D tex = GetComponent<SpriteRenderer>().sprite.texture;
        //Debug.Log(Random.Range(0, tex.width - 1));
        //tex.SetPixel(Random.Range(0, tex.width-1), Random.Range(0, tex.height - 1), Color.black);
        //tex.Apply();

        UpdateCellMatrix(); // Very slow, but could be optimized.
        DrawCellMatrix(); // Reasonably fast on its own.

        // Code to get texture coordinates where mouse clicked on sprite:

        /*
        SpriteRenderer renderer = hit.collider.GetComponentInChildren<SpriteRenderer>();
        Texture2D tex = renderer.sprite.texture;

        //Get hit position (placeholder)
        Vector2 pixelUV = hit.textureCoord;
        pixelUV.x *= tex.width;
        pixelUV.y *= tex.height;
        */

        // Instantiate a copy of the texture so when we write to it we don't modify the texture on disk.
        // This may not be necessary if we just create the texture from scratch.

        //Texture2D newTex = new Texture2D (tex.width, tex.height, TextureFormat.ARGB32, false);
        //newTex.SetPixels32(tex.GetPixels32());

        // Texture2D newTex = (Texture2D)GameObject.Instantiate(tex); // Faster

        // Should I be doing this every update? I don't think so. It will overwrite my last change.

        //newTex.SetPixel((int) pixelUV.x, (int) pixelUV.y, Color.black); // Use SetPixel32. Better yet use SetPixels32.

        //newTex.Apply(); // This applies the changes we made to the texture in RAM by copying them to texture memory.


        // renderer.sprite = Sprite.Create(newTex, renderer.sprite.rect, new Vector2(0.5f, 0.5f)); // Not sure what this is for aside from displaying the new texture.



    }

}

Oh and one more thing… Even though I believe I have set my camera size correctly to scale the texture to fit the screen, and the texture is not scaled down, and I have looked at it with magnification in the editor to be sure… my pixels aren’t coming out as black and white. They’re shades of gray. Almost as if the texture is displaying the wrong mip level. It could just be my crappy graphics card though. I really need to upgrade it. It’s like from 2010.

The reason I suspect it is because when I did the ball roll demo, the shadowed areas of the ball were splotchy. Like, reddish and bluish splotches. I have to assume it’s some incompatibility with my graphics driver or the shaders Unity is using. I don’t want to upgrade my graphics card just yet either because I plan to get a VR setup eventually so I don’t want to waste money on a high end card when a better one is probably going to come out before I buy the headset.

1 Like

That’s a good idea. Many simulators (I mean even the ones that aren’t closely related to cell-based things) do this in one way or another.

The PhysX engine puts objects to “sleep”. Which isn’t quite the same as skipping a region, but its a similar approach.

1 Like

Maybe you removed the colliders on accident?
I think you need those unless you’re working with a Canvas/UI (as that does thing differently)

As long as C# relies on an interpreter while C++ compiles to native code, C# will never be as fast as C++. Unless you use IL2CPP, in which case, we’re not really talking about C# anymore.

Having said that, you pretty much need to do complex operations for the performance difference between C# and C++ to really matter these days. You can get pretty fast in C# using unsafe code (just be sure you know what you’re doing).

I’ve been able to write bitmap analyzers in C# to scan the pixels for blown highlights and still keep the app running in triple-digit FPS. Native code was still needed for complex image operations such as hough line detection, converting images to 3D meshes, etc.

Also keep in mind that there may be a slight overhead in going from managed to native, and back again. You need to be sure that whatever performance gains you make using native code won’t be cancelled out by that overhead.

Are you saying that Unity doesn’t do the basic just-in-time compilation that .NET and vanilla Mono both do?

There’s no interpreter, that’s complete nonsense.

C# gets compiled to CIL, which gets jit-ed at runtime, in the end both C# and C++ create native code.
And “never be as fast as c++” is debatable. Sure there are some more optimizations that could be done in the .NET jit compiler, but the performance is definitely comparable.

I’m not questioning the usage of C++ there, but note that C# also has unsafe pointers etc…
If you really wanted to, you could do the same low-level things in C#.

Actually, that’s a question I’m not entirely sure about. I would think (hope!) it does JIT the same way, but since it’s an old (and hacked) version of Mono, I don’t think I could give a solid answer to that without digging into it further.

However, there was a time when Apple forbade interpreters (which included JIT interpreters) for iOS platforms, so code had to be AOT compiled for that platform. I’m not sure if that’s still the case today, though. (And before anyone asks, no this does not mean it’s the fastest because it’s AOT compiled, as that has its own set of issues and must target the lowest common denominator in the iOS ecosystem).

Technically speaking, with the JIT compiler, there is overhead to do a cache lookup vs compiling never-before-executed methods (or it might just re-compile if it thinks it’s faster), but it’s so laughably minimal it’s not really worth thinking about, much less making you decide to turn your whole project into C++. Don’t even try to beat the JIT compiler. You won’t be able to.

See previous post. There’s technically speaking, and there’s realistically speaking. I was speaking in terms of technically speaking.

But realistically speaking? It’s not worth it.

EDIT: It also seems you took issue with my usage of the word “interpreter”, which is fair enough. I was thinking of the JIT compiler logic that determines whether code needs to be compiled, re-compiled, or retrieved from the cache. I started thinking of that as an interpreter, which is where I’m coming from.

I also addressed that:

1 Like

As @dadude123 points out, raycasting works with colliders. So you’d need to have a collider on your sprite.

An alternative, when doing neat 2D stuff like this, is to just take the mouse position and do the math to calculate what cell it would be over. As long as you’re using an orthographic camera, and you’ve been precise about how you position the sprite on screen, this will work fine. (And if you haven’t, well, that’s probably something you’ll want to fix sooner or later anyway.)

Possible. You shouldn’t have any mips defined on this texture, though.

More likely it’s a shader effect. Make sure you’re using the Sprite shader, and that the sprite color is set to pure white. Most other shaders apply lighting, which you don’t want here. (In fact, take all lights out of your scene — that’s a pretty sure way to make sure you’re not using a shader affected by lighting.)