结构类型数组的性能

时间:2017-04-28 17:02:23

标签: c# arrays performance struct

示例:

// Potentially large struct.
struct Foo
{ 
   public int A;
   public int B;
   // etc.
}

Foo[] arr = new Foo[100];

如果Foo是100字节结构,则在执行以下语句期间将在内存中复制多少字节:

int x = arr[0].A

也就是说,将arr [0]计算为某个临时变量(Foo实例的100字节副本),然后将.A复制到变量x(4字节副本)。

或者是编译器,JITer和CLR的某种组合能够优化此语句,使A的4个字节直接复制到x

如果执行了优化,当项目保存在List<Foo>中或数组作为IList<Foo>ArraySegment<Foo>传递时,它是否仍然存在?

2 个答案:

答案 0 :(得分:9)

值类型按值复制 - 因此名称。那么我们必须考虑必须在何时复制一个值。这归结为在特定实体引用变量时正确分析。如果它引用了一个值,那么该值是从某个地方复制的。如果它引用一个变量,那么它只是一个变量,并且可以像任何其他变量一样对待。

假设我们有

struct Foo { public int A; public int B; }

暂时忽略设计缺陷;公共领域是一种糟糕的代码气味,可变结构也是如此。

如果你说

Foo f = new Foo();

会发生什么?规范说:

  • 创建了一个新的八字节变量f
  • 创建一个临时的八字节存储位置temp
  • temp填入八个字节的零。
  • temp已复制到f

但事实并非如此;编译器和运行时很聪明,可以注意到所需的工作流程和工作流程之间没有可观察到的差异&#34;创建f并用零填充它&#34;,这样就发生了。这是复制省略优化

练习:设计一个编译器无法复制的程序,并且输出清楚地表明编译器在初始化struct类型的变量时不执行复制省略。

现在,如果你说

f.A = 123;

然后计算f以生成变量 - 而不是值 - 然后从A计算生成变量,并将四个字节写入该变量。

如果你说

int x = f.A;

然后f被评估为变量,A被评估为变量,A的值被写入x

如果你说

Foo[] fs = new Foo[1];

然后分配变量fs,分配数组并用零初始化,并将对数组的引用复制到fs。当你说

fs[0].A = 123;

和以前一样。 f[0]被评估为变量,因此A变量,因此将123复制到该变量。

当你说

int x = fs[0].A;
与以前一样:我们将fs[0]评估为变量,从该变量中获取A的值,然后复制它。

但如果你说

List<Foo> list = new List<Foo>();
list.Add(new Foo());
list[0].A = 123;

然后您将收到编译器错误,因为list[0],而不是变量。你无法改变它。

如果你说

int x = list[0].A;

然后将list[0]评估为作为值 - 制作列表中存储的值的副本 - 然后在A中创建x的副本{1}}。所以这里有一个额外的副本。

练习:编写一个程序,说明list[0]是列表中存储的值的副本。

正是出于这个原因,你应该(1)不要做大结构,(2)使它们不变。结构被值复制,这​​可能很昂贵,而且值不是变量,所以很难改变它们。

  

是什么让数组索引器返回一个变量而不是列表索引器?阵列是以特殊方式处理的吗?

是。数组是非常特殊的类型,它们深深地构建在运行时中并且从版本1开始。

这里的关键特性是数组索引器在逻辑上为数组中包含的变量生成别名;然后,该别名可以用作变量本身。

所有其他索引器实际上是get / set方法对,其中get返回一个值,而不是一个变量。

  

我可以创建自己的类,在这方面的行为与数组相同

在C#7之前,不在C#中。您可以在IL中执行此操作,但当然C#也不知道如何处理返回的别名。

C#7增加了方法返回变量别名的能力:ref返回。请记住,ref(和out参数将变量作为操作数,并使被调用者拥有该变量的别名。 C#7还为 locals 返回添加了此功能。

答案 1 :(得分:1)

整个结构已经在内存中。当您访问arr[0].A时,您没有复制任何内容,也不需要新的内存。您正在查找对象引用(可能在调用堆栈上,但结构可能也被堆上的引用类型包装)以查找arr[0]的位置,并调整{的偏移量{1}}属性,然后只访问该整数。没有必要阅读完整的结构只是为了得到A。

到目前为止,AList<Foo>都没有真正改变任何重要内容。

但是,如果您将ArraySegment<Foo>传递给函数或将其分配给新变量,则会导致复制arr[0]对象。这是.Net中struct(值类型)和类(引用类型)之间的一个区别;一个类只会复制引用,FooList<Foo>都是引用类型。

在.Net中,尤其是作为平台的新手,你应该在大多数情况下强烈地选择ArraySegment<Foo>而不是class,而不仅仅是复制整个对象与复制引用。还有一些其他微妙的语义差异,即使我承认也不完全理解。请记住该课程&gt;结构,直到你有一个很好的经验理由改变主意。