在C#中使用struct时是否有任何内存或CPU开销

时间:2013-04-24 18:14:28

标签: c# memory struct

使用struct shim替换锯齿状数组是否有任何开销?

举一个具体的例子

vertices = new KeyValuePair<uint, EdgeData>[][];

VS

private struct Vertex
{
    public KeyValuePair<uint, EdgeData>[] Arcs { get; set; }
}

vertices = new KeyValuePair<uint, Vertex>[];

如果EdgeData有任何不同,则它是一个类 显然,结构示例中的意图更清晰,但它需要能够保存大量图形,因此任何内存开销都很重要

3 个答案:

答案 0 :(得分:4)

可以在堆栈上分配或不分配struct。引用类型可以永远在堆栈上分配;它们总是在堆上分配。

来自标准(ISO 23270),§8.8:

  

8.8结构   类和结构之间的相似性列表是长结构可以实现的   接口,可以与类具有相同类型的成员。结构不同于   但是,几个重要方面的类:结构是值类型而不是   结构类型和结构不支持继承。结构值   存储在“堆栈”或“在线”。细心的程序员有时可以提升   通过明智地使用结构来表现。

     

例如,对Point使用结构而不是类可以做大   在运行时执行的内存分配数量的差异。该程序   下面创建并初始化一个包含100个点的数组。

     

Point作为一个类实现,实例化101个独立的对象 - 一个用于   数组和100个元素各一个。

class Point
{
  public int x, y;
  public Point(int x, int y)
  {
    this.x = x;
    this.y = y;
  }

}
class Test
{
  static void Main()
  {
    Point[] points = new Point[100];
    for (int i = 0; i < 100; i++)
    {
      points[i] = new Point(i, i*i);
    }
}
     

如果将Point实现为结构,如

struct Point
{
  public int x, y;
  public Point(int x, int y)
  {
    this.x = x;
    this.y = y;
  }
}
     

只实例化一个对象 - 数组的对象。 Point实例是   在数组中内联分配。这种优化可能被滥用。使用结构   而不是类也可以使应用程序运行更慢或占用更多的内存,   因为按值传递struct实例会导致创建该结构的副本。

所以答案是“可能”。

对于您的示例,在struct(值类型)中包装数组(引用类型)并不意味着什么:该数组仍在堆上分配。

但是,如果将类EdgeData更改为结构,则可以(但可能不是)在数组中内联分配。因此,如果您的EdgeData类的大小为16个字节,并且您创建并填充了100个条目的EdgeData[],则实际上是在分配1个数组实例(后备存储大小为100对象引用,以及EdgeData类的100个单独实例。

如果EdgeData是结构,则分配1个数组,其后备存储大小可容纳100 EdgeData个实例(在本例中为1600字节,因为我们的假设EdgeData结构为16字节大小。)

迭代数组的类版本,特别是如果数组非常大,可能会导致分页,因为当你跳过整个堆来命中单个EdgeData实例时,你可能会失去引用的局部性。

struct版本的数组进行迭代会保留引用的位置,因为EdgeData实例是内联的。

答案 1 :(得分:2)

使用1D结构数组替换2D数组不会导致任何问题。这真的是你如何查看数据的问题。如果将它建模为一个结构数组更有意义,每个结构都包含一个弧数组,那么就应该在代码中表达它。

存储它们的方式存在一些细微差别。特别是,与2D阵列方法相比,您的一维阵列方法将占用更多内存。基本上,每行都有一个额外的uint

袭来之后。它正在讨论结构方法和2D数组(即[,])之间的区别,而不是OP正在使用的锯齿状数组([][])。

实际上,使用的总内存不止于此。在2D数组方法中,数组中有(row * col) KeyValuePair个结构。该阵列在64位运行时具有大约50个字节的分配开销(如果我记得,在32位运行时中大约40个字节)。在1D数组方法中,您仍然具有(row * col) KeyValuePair结构,但每个结构都包含一个具有相同50字节分配开销的数组。此外,您拥有vertices数组,其中包含(row) KeyvaluePair个结构。

但是,您的2D数组(只是数组)需要(rows * cols * (4 + sizeof(IntPtr)))个字节。 1D vertices数组只需要(rows * (4 + sizeof(IntPtr)))个字节。如果您对单个阵列的限制为2千兆字节(因为您在.NET 4.0及更早版本中,或者在.NET 4.5中,除非您启用了非常大的对象),那么使用1D阵列可能会有更多项目,总计结构比2D数组。当然,假设您有足够的内存来容纳那么多KeyValuePair<uint, EdgeData>个实例。

因此,您的整体内存使用量将会增加,但您最大的单个分配将会小得多。

答案 2 :(得分:2)

结构数组往往效率相当高,但在您的特定示例中,每行都有一个额外的uint。此外,避免暴露结构类型的属性,如果结构表示与管道胶带绑定在一起的独立值的集合(例如,点的坐标),则只需将这些项作为字段公开。虽然在很多情况下JIT会将属性访问转换为字段访问,但也有很多情况下它不能。

如果要比较效率:

struct FloatPoint2D {public float X,Y;}
FloatPoint3D[] MyArray;

float[] MyXCoords, MyYCoords;

使用上面定义的结构以随机顺序访问项目的X和Y将比使用一对单独的数组(通常是一个缓存未命中而不是两个)更快但仅访问X或仅访问许多的Y坐标如果使用单独的数组,则序列中的项将更快(每个缓存行将获取两倍的有用的坐标)。

在您的特定示例中,不清楚您的类型需要封装哪些数据;你的struct和non-struct示例包含不同的数据,所以很难说一个“更高效”。