I work with cellular automata. My repo for my work is here. The basic structure is
1) A grid of 2) cells, which may have 3) agents.
The agents act according to a set of rules, and typically one designates "states" for the agents (agents of different states have different rules). One (relatively) well-known CA is the game of life.
I'm trying to expand things a bit more and incorporate other types of "properties" in my CAs, mainly to simulate various phenomena (imagine an animal agent that consumes a plant agent, with the plant's "biomass" or what have you decreasing).
To do this I'm incorporating a normal dictionary, with strings as keys and a struct called CAProperty as the value. The struct is as follows:
public struct CAProperty
{
public readonly string name;
public readonly dynamic value;
//public readonly Type type;
public CAProperty(string name, dynamic value)
{
this.name = name;
this.value = value;
}
}
(note: previously I had the "type" variable to enable accurate typing at runtime...but in attempts to solve the issue in this post I removed it. Fact is, it'll need to be added back in)
This is well and good. However, I'm trying to do some work with large grid sizes. 100x100. 1000x1000. 5000x5000, or 25 million cells (and agents). That would be 25 million dictionaries.
See the image: a memory snapshot from Visual Studio for a 4000x4000 grid, or 16 million agents (I tried 5000x5000, but Visual Studio wouldn't let me take a snapshot).
On the right, one can clearly see that the debugger is reading 8 GB memory usage (and I tried this in a release version to see 6875 MB usage). However, when I count up everything in the third column of the snapshot, I arrive at less than 4 GB.
Why is there such a dramatic discrepancy between the total memory usage and the size of objects stored in memory?
Additionally: how might I optimize memory usage (mainly the dictionaries - is there another collection with similar behavior but lower memory usage)?
Edit: For each of the three "components" (Grid, Cell, Agent) I have a class. They all inherit from an original CAEntity class. All are shown below.
public abstract class CAEntity
{
public CAEntityType Type { get; }
public Dictionary<string, dynamic> Properties { get; private set; }
public CAEntity(CAEntityType type)
{
this.Type = type;
}
public CAEntity(CAEntityType type, Dictionary<string, dynamic> properties)
{
this.Type = type;
if(properties != null)
{
this.Properties = new Dictionary<string, dynamic>(properties);
}
}
}
public class CAGraph:CAEntity
{
public ValueTuple<ushort, ushort, ushort> Dimensions { get; }
public CAGraphCell[,,] Cells { get;}
List<ValueTuple<ushort, ushort, ushort>> AgentCells { get; set; }
List<ValueTuple<ushort, ushort, ushort>> Updates { get; set; }
public CA Parent { get; private set; }
public GridShape Shape { get; }
//List<double> times = new List<double>();
//System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
public CAGraph (CA parent, ValueTuple<ushort, ushort, ushort> size, GridShape shape):base(CAEntityType.Graph)
{
this.Parent = parent;
this.Shape = shape;
AgentCells = new List<ValueTuple<ushort, ushort, ushort>>();
Updates = new List<ValueTuple<ushort, ushort, ushort>>();
Dimensions = new ValueTuple<ushort, ushort, ushort>(size.Item1, size.Item2, size.Item3);
Cells = new CAGraphCell[size.Item1, size.Item2, size.Item3];
for (ushort i = 0; i < Cells.GetLength(0); i++)
{
for (ushort j = 0; j < Cells.GetLength(1); j++)
{
for (ushort k = 0; k < Cells.GetLength(2); k++)
{
Cells[i, j, k] = new CAGraphCell(this, new ValueTuple<ushort, ushort, ushort>(i, j, k));
}
}
}
}
public CAGraph(CA parent, ValueTuple<ushort, ushort, ushort> size, GridShape shape, List<ValueTuple<ushort, ushort, ushort>> agents, CAGraphCell[,,] cells, Dictionary<string, dynamic> properties) : base(CAEntityType.Graph, properties)
{
Parent = parent;
Shape = shape;
AgentCells = agents.ConvertAll(x => new ValueTuple<ushort, ushort, ushort>(x.Item1, x.Item2, x.Item3));
Updates = new List<ValueTuple<ushort, ushort, ushort>>();
Dimensions = new ValueTuple<ushort, ushort, ushort>(size.Item1, size.Item2, size.Item3);
Cells = new CAGraphCell[size.Item1, size.Item2, size.Item3];
for (ushort i = 0; i < size.Item1; i++)
{
for (ushort j = 0; j < size.Item2; j++)
{
for (ushort k = 0; k < size.Item3; k++)
{
//if(i == 500 && j == 500)
//{
// Console.WriteLine();
//}
Cells[i, j, k] = cells[i, j, k].Copy(this);
}
}
}
}
}
public class CAGraphCell:CAEntity
{
public CAGraph Parent { get; set; }
public CAGraphCellAgent Agent { get; private set; }
public ValueTuple<ushort, ushort, ushort> Position { get; private set; }
//private Tuple<ushort, ushort, ushort>[][] Neighbors { get; set; }
//System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
public CAGraphCell(CAGraph parent, ValueTuple<ushort, ushort, ushort> position):base(CAEntityType.Cell)
{
this.Parent = parent;
this.Position = position;
//this.Neighbors = new Tuple<ushort, ushort, ushort>[Enum.GetNames(typeof(CANeighborhoodType)).Count()][];
}
public CAGraphCell(CAGraph parent, ValueTuple<ushort, ushort, ushort> position, Dictionary<string, dynamic> properties, CAGraphCellAgent agent) :base(CAEntityType.Cell, properties)
{
this.Parent = parent;
this.Position = position;
if(agent != null)
{
this.Agent = agent.Copy(this);
}
}
}
public class CAGraphCellAgent:CAEntity
{
// have to change...this has to be a property? Or no, it's a CAEntity which has a list of CAProperties.
//public int State { get; set; }
public CAGraphCell Parent { get; set; }
//System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
public CAGraphCellAgent(CAGraphCell parent, ushort state):base(CAEntityType.Agent)
{
this.Parent = parent;
AddProperty(("state", state));
}
public CAGraphCellAgent(CAGraphCell parent, Dictionary<string, dynamic> properties) :base(CAEntityType.Agent, properties)
{
this.Parent = parent;
}
}