Suppose one wants to hold one million 3d points, and allow the following operations:
double GetX(int index)
// And likewise GetY and GetZ
double SetX(int index, double value)
// And likewise SetY and SetZ
double SetXYZ(int index, double x, double y, double z)
void CopyCoord(int src, int dest)
If one uses a mutable structure type:
struct Point3dStruct { public double X,Y,Z; }
Point3dStruct[] array;
the operations would become:
void init()
{
array = new Point3dStruct[1000000];
}
double GetX(int index)
{ return array[index].X; }
double SetX(int index, double value)
{ array[index].X = value; }
double SetXYZ(int index, double x, double y, double z)
{ array[index].X = x; array[index].Y = y; array[index].Y = z; }
void CopyCoord(int src, int dest)
{ array[dest] = array[src]; }
All operations would be reasonably efficient; 24,000,000 bytes would be required to hold 1,000,000 points, regardless of whether some or all of them were the same or different.
Using a so-called "immutable" struct would require changing the SetX
and SetXYZ
methods:
double SetX(int index, double value)
{
Point3dStruct temp = array[index];
array[index] = new Point3dStruct(value, temp.Y, temp.Z);
}
double SetXYZ(int index, double x, double y, double z)
{
array[index] = new Point3dStruct(x, y, z);
}
Performance for SetX
would be much inferior to that of a simple exposed-field struct; no method would perform better than the exposed-field-struct equivalent. Memory requirements would not be affected by whether the struct was mutable or not.
A mutable class would require code much like a mutable struct except for the init
and CopyCoord
methods.
void init()
{
array = new Point3dClass[1000000];
for (int i=0; i<1000000; i++)
array[i] = new Point3dClass();
}
void CopyCoord(int src, int dest)
{
array[dest].X = array[src].X;
array[dest].Y = array[src].Y;
array[dest].Z = array[src].Z;
}
Note that accidentally writing array[dest] = array[src]
would not copy the values, but would instead totally break the code! Memory usage would require an extra 16 or 32 bytes per element on 32-bit or 64-bit machines (i.e. 16,000,000 or 32,000,000 bytes) regardless of whether all points held the same or different values.
Use of an immutable class would require code similar to an immutable struct, except for the init
method:
void init()
{
array = new Point3dClass[1000000];
var zero = new Point3dClass(0.0, 0.0, 0.0);
for (int i=0; i<1000000; i++)
array[i] = zero;
}
Initial memory usage would only be about 4,000,000 or 8,000,000 bytes (on 32- or 64-bit machines, respectively), but every separately-created instance of Point3dClass
would add another 12 or 24 bytes. If the array holds references to 1,000,000 different instances of Point3dClass
, those would total up to another 12,000,000 or 24,000,000 bytes.
If code will be using methods analogous to CopyCoord
more often than it will be using methods analogous to SetX
, then an immutable class can be a big win. If it will be using SetX
a lot, an exposed-field mutable struct will offer the best performance. Mutable class types may play nicer than mutable structs when stored in collections other than arrays, but they have a substantial performance overhead and must be used with extreme care. The only advantage of immutable structs is that code which is written for an immutable struct can often be changed easily to use an immutable class instead.