Introduction
In game development, optimizing code is essential for ensuring smooth gameplay. This is particularly true in Unity, where memory management and execution speed are crucial. The methods used to search and filter large datasets can significantly impact application performance. This article compares three approaches for searching for elements in lists: using classes, dictionaries, and structs. We will investigate how these techniques perform in scenarios that require fast filtering of large collections of data.
Sample Class: TileData
Let’s begin with the TileData
class, which represents tile objects. Each tile has a position, type, and amount, forming the basic structure of our data:
public class TileData
{
public int PositionX { get; private set; }
public int PositionY { get; private set; }
public TileType Type { get; private set; }
public int Amount { get; private set; }
public TileData(int positionX, int positionY, TileType type, int amount)
{
PositionX = positionX;
PositionY = positionY;
Type = type;
Amount = amount;
}
}
public enum TileType
{
Water,
Rock,
Snow,
Grass,
Air
}
Data Initialization
For testing purposes, we create a list of TileData
objects with 1,000,000 randomly generated entries. Each object is assigned a random TileType
, giving us a realistic example of a large collection to filter.
private void InitTiles()
{
AllTiles = new List<TileData>();
System.Array tileTypes = System.Enum.GetValues(typeof(TileType));
System.Random random = new System.Random(100);
for (int x = 0; x < 1000; x++)
{
for (int y = 0; y < 1000; y++)
{
TileType randomType = (TileType)tileTypes.GetValue(random.Next(tileTypes.Length));
int randomAmount = random.Next(1, 101);
TileData tile = new TileData(x, y, randomType, randomAmount);
AllTiles.Add(tile);
}
}
matchingTiles = new List<TileData>(1000 * 1000);
}
Searching with Class
The method below searches for tiles using the class-based approach, employing a linear search through the list. While straightforward, this method can result in longer execution times when dealing with large datasets.
public List<TileData> SearchTiles(TileType tileType, int amount)
{
matchingTiles.Clear();
foreach (var tile in AllTiles)
{
if (tile.Type == tileType && tile.Amount >= amount)
{
matchingTiles.Add(tile);
}
}
return matchingTiles;
}
Searching with Dictionary
Next, we can improve the search performance by using a dictionary to store tiles by their type. This allows for faster lookups compared to a linear search.
public List<TileData> SearchTilesInDictionary(TileType tileType, int amount)
{
matchingTiles.Clear();
if (tileDictionary.TryGetValue(tileType, out var tiles))
{
foreach (var tile in tiles)
{
if (tile.Amount >= amount)
{
matchingTiles.Add(tile);
}
}
}
return matchingTiles;
}
Changing to Structs
Now let’s explore the performance benefits of using structs instead of classes. Structs are value types, meaning they are stored on the stack rather than the heap. This can lead to improved performance when working with large collections of data.
public struct TileData
{
public int PositionX { get; private set; }
public int PositionY { get; private set; }
public TileType Type { get; private set; }
public int Amount { get; private set; }
public TileData(int positionX, int positionY, TileType type, int amount)
{
PositionX = positionX;
PositionY = positionY;
Type = type;
Amount = amount;
}
}
Conclusion
This analysis highlights how selecting an appropriate data structure and search method can greatly enhance performance in Unity.
- Classes: Best suited for more complex, long-lived objects that benefit from features like inheritance. Because classes are reference types, they are allocated on the heap, resulting in higher memory overhead and slower performance in data-intensive tasks.
- Dictionaries: Using dictionaries allows for efficient, constant-time lookups, which reduces the need for linear scans. This makes them ideal when fast retrieval of grouped data is needed, significantly improving search performance in scenarios with frequent lookups.
- Structs: Well-suited for small, homogeneous data sets that require fast access and are frequently created and destroyed. As value types, structs are allocated on the stack, minimizing memory overhead and improving cache locality, which can lead to faster execution times compared to classes.
Selecting the best data structure depends on the specific needs of your project. Testing each approach using Unity’s Profiler is crucial to achieve a balance between performance and memory usage, ensuring efficient gameplay and resource management.