结构类型数组的性能

例:

// 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或者数组作为IListArraySegment传递时,它是否仍然成立?

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

假设我们有

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

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

如果你说

 Foo f = new Foo(); 

怎么了? 规范说:

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

但事实并非如此; 编译器和运行时足够聪明,注意到所需的工作流和工作流“创建f并用零填充”之间没有可观察到的差异,所以这种情况发生了。 这是一个复制省略优化

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

现在,如果你说

 fA = 123; 

然后对f进行求值以产生一个变量 – 而不是一个值 – 然后从中评估A以产生一个变量,并将四个字节写入该变量。

如果你说

 int x = fA; 

然后将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 list = new List(); list.Add(new Foo()); list[0].A = 123; 

然后你会得到编译器错误,因为list[0]是一个 ,而不是一个变量 。 你无法改变它。

如果你说

 int x = list[0].A; 

然后将list[0]计算为一个值 – 存储在列表中的值的副本 – 然后在x中创建A的副本。 所以这里有一个额外的副本。

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

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

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

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

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

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

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

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

C#7增加了方法将别名返回变量的能力: ref返回。 请记住, ref (和out参数将变量作为其操作数,并使被调用者具有该变量的别名。 C#7增加了对本地人返回这样做的能力。

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

到目前为止, ListArraySegment真正改变任何重要的东西。

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

在.Net中,特别是作为平台的新手,你应该在大多数时候都非常喜欢class over struct ,而不仅仅是复制整个对象与复制引用。 还有一些其他微妙的语义差异,即使我承认也不完全理解。 只要记住那个类>结构,直到你有一个很好的经验理由来改变你的想法。