8
\$\begingroup\$

I'm working on a tile-based game, and am looking to improve the efficiency of my code to slightly improve framerate. I call this method on every tile, every frame, so it is very performance critical. What optimizations can be made?

public bool OnScreen(Vector2 point) 
{
    Rectangle rect = this.Rectangle;
    return ((((rect.X <= point.X) && (point.X < (rect.X + rect.Width))) && (rect.Y <= point.Y)) && (point.Y < (rect.Y + rect.Height)));
}

public bool OnScreen(Vector2 topLeft, Vector2 size)
{
    // Check if each corner is inside of the camera rectangle
    return (
        OnScreen(topLeft) // Top-left
        || OnScreen(topLeft + new Vector2(0, size.Y)) // Bottom-left
        || OnScreen(topLeft + new Vector2(size.X, 0)) // Top-right
        || OnScreen(topLeft + size) // Bottom-right
    );
}

I am doing this so that I can pool the objects that actually render my tiles, since only a maximum of about 800 can be on screen at any one time. These methods are called as so:

// Assign renderers to any tiles in range of the camera.
foreach (var tile in _tiles)
{
    if (tile.IsInRange(camera))
    {
        if (tile.Renderer == null)
        {
            tile.Renderer = _pool.Get();
        }
    }
    else 
    {
        tile.Renderer = null; 
    }
}
\$\endgroup\$

4 Answers 4

10
\$\begingroup\$

You could try to use the following built-in methods instead:

Thus your latter OnScreen method will become:

public bool OnScreen(Vector2 topLeft, Vector2 size)
{
    return this.Rectangle.IntersectsWith(new Rectangle(topLeft.X, topLeft.Y, size.X, size.Y));
}

Source code at referencesource.microsoft.com:

\$\endgroup\$
3
  • \$\begingroup\$ Ah, good catch! And that just mootinated my answer! :-) \$\endgroup\$ Commented May 28, 2015 at 21:36
  • 1
    \$\begingroup\$ I'm marking this as accepted because that is a huge improvement. Since I'm not using the System.Drawing rectangle but rather the Monogame rectangle, I'm going to see if there is a similar method. Could you add the actual logic behind that method call to your answer? \$\endgroup\$
    – Pip
    Commented May 28, 2015 at 21:38
  • 1
    \$\begingroup\$ @Pip Ah, my bad. I've added links to the source codes. \$\endgroup\$
    – Dmitry
    Commented May 28, 2015 at 21:50
11
\$\begingroup\$

At a glance, OnScreen looks like a method that would raise some Screen event, as On[EventName] is, by convention, the name we use for methods that raise an event. Now, those would obviously return void, and yours is returning a bool - I think I would have it like this:

public bool IsOnScreen(Vector2 point)

The return statement is pretty intense in terms of parentheses. Are they meant to improve readability?

return ((((rect.X <= point.X) && (point.X < (rect.X + rect.Width))) && (rect.Y <= point.Y)) && (point.Y < (rect.Y + rect.Height)));

Let's try something:

return rect.X <= point.X 
    && point.X < (rect.X + rect.Width) 
    && rect.Y <= point.Y 
    && point.Y < (rect.Y + rect.Height);

Much better isn't it?

\$\endgroup\$
4
\$\begingroup\$

for vs foreach

If _tiles is not an array you can gain some performance by using a simple for(int index = 0; index < _tiles.Count; index++).

This is because foreach uses the IEnumerable<out T>.GetEnumerator which can result in a lot of extra method calls in a tight loop. When the compiler can verify that the foreach is used on an array it will optimize it into the equivalent for-loop.


This short but non scientific example, compiled in release x64 in vs2012 and run outside of Visual Studio, shows that foreach is just short of three times as slow as for on a list of 1 million elements:

class Program
{
    static void Main()
    {
        var totalTime = Stopwatch.StartNew();
        var list = Enumerable.Range(0, 1000000).ToList();

        long sum = 0;
        //warmup
        for (int i = 0; i < 10000; i++)
        {
            sum = UsingFor(sum, list);
            sum = UsingForeach(sum, list);
        }

        sum = 0;
        var stopwatch = Stopwatch.StartNew();
        sum = UsingForeach(sum, list);
        stopwatch.Stop();
        Console.WriteLine("Foreach-result=" + sum + " found in " + stopwatch.Elapsed);
        sum = 0;
        stopwatch.Restart();
        sum = UsingFor(sum, list);
        stopwatch.Stop();
        Console.WriteLine("For-result=" + sum + " found in " + stopwatch.Elapsed);
        Console.WriteLine("TotalTime: " + totalTime.Elapsed);
    }

    static long UsingFor(long sum, List<int> list)
    {
        for (int i = 0; i < list.Count; i++)
        {
            sum += list[i];
        }
        return sum;
    }

    static long UsingForeach(long sum, List<int> list)
    {
        foreach (var value in list)
        {
            sum += value;
        }
        return sum;
    }
}

On my computer it's 39 seconds of warmup and

  • Foreach-result found in 00:00:00.0029817
  • For-result found in 00:00:00.0009497

This is only a measure of the iteration cost which very well might be dwarfed by the cost of what is being calculated in the loop.

\$\endgroup\$
2
  • \$\begingroup\$ Huh, I didn't know that. How much of a performance difference is this roughly? \$\endgroup\$
    – Pip
    Commented May 29, 2015 at 13:53
  • 2
    \$\begingroup\$ It's very hard to tell but around 3 additional method invocations per iteration. A few nanoseconds at worst but for a loop over 1 million elements it does add up. It might also allow the compiler to unroll the loop and do several comparisons per iteration which can result in better utilization of the CPU pipelines. TL:DR; Use a profiler. \$\endgroup\$
    – Johnbot
    Commented May 29, 2015 at 13:59
4
\$\begingroup\$

Change the design?

I don't know the details of your game, so this might be silly...

A tile game is generally easily organized as a grid:

  • Store all tiles in a matrix
  • Depending on how your camera and your rendering works, you can find the top-left and bottom-right coordinates, in that matrix, of visible tiles.
  • Render only tiles in the area that is visible, with two nested for loops. This is faster than checking, for each tile, whether it should be rendered.
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.