结构数组与对象数组 C#
本文关键字:数组 对象 结构 | 更新日期: 2023-09-27 18:36:27
我知道可变结构是邪恶的。但是,我仍然想比较结构数组与对象数组的性能。这就是我到目前为止所拥有的
public struct HelloStruct
{
public int[] hello1;
public int[] hello2;
public int hello3;
public int hello4;
public byte[] hello5;
public byte[] hello6;
public string hello7;
public string hello8;
public string hello9;
public SomeOtherStruct[] hello10;
}
public struct SomeOtherStruct
{
public int yoyo;
public int yiggityyo;
}
public class HelloClass
{
public int[] hello1;
public int[] hello2;
public int hello3;
public int hello4;
public byte[] hello5;
public byte[] hello6;
public string hello7;
public string hello8;
public string hello9;
public SomeOtherClass[] hello10;
}
public class SomeOtherClass
{
public int yoyo;
public int yiggityyo;
}
static void compareTimesClassVsStruct()
{
HelloStruct[] a = new HelloStruct[50];
for (int i = 0; i < a.Length; i++)
{
a[i] = default(HelloStruct);
}
HelloClass[] b = new HelloClass[50];
for (int i = 0; i < b.Length; i++)
{
b[i] = new HelloClass();
}
Console.WriteLine("Starting now");
var s1 = Stopwatch.StartNew();
for (int i = 0; i < _max; i++)
{
a[i % 50].hello1 = new int[] { 1, 2, 3, 4, i % 50 };
a[i % 50].hello3 = i;
a[i % 50].hello7 = (i % 100).ToString();
}
s1.Stop();
var s2 = Stopwatch.StartNew();
for (int j = 0; j < _max; j++)
{
b[j % 50].hello1 = new int[] { 1, 2, 3, 4, j % 50 };
b[j % 50].hello3 = j;
b[j % 50].hello7 = (j % 100).ToString();
}
s2.Stop();
Console.WriteLine(((double)(s1.Elapsed.TotalSeconds)));
Console.WriteLine(((double)(s2.Elapsed.TotalSeconds)));
Console.Read();
}
这里有几件事我想了解。
首先,由于数组存储结构,当我尝试使用索引操作从数组访问结构时,我应该获取结构的副本还是对原始结构的引用? 在这种情况下,当我在运行代码后检查数组时,我得到了突变的结构值。为什么会这样?
其次,当我比较CompareTimesClassVsStruct()
内部的时间时,我得到的时间大致相同。这背后的原因是什么?是否存在使用结构数组或对象数组优于其他结构
谢谢
当您访问结构数组的元素的属性时,您不是在对结构的副本进行操作 - 而是在对结构本身进行操作。(对于您将在副本上进行操作的List<SomeStruct>
,情况并非如此,并且示例中的代码甚至无法编译。
您看到相似时间的原因是,这些时间被循环中的(j % 100).ToString()
和new int[] { 1, 2, 3, 4, j % 50 };
扭曲了。这两个语句所花费的时间使数组元素访问所花费的时间相形见绌。
我稍微更改了测试应用程序,并且我获得了访问 9.3s 的结构数组和 10s 的类数组(1,000,000,000 个循环)的时间,因此结构数组明显更快,但非常微不足道。
可以使结构数组更快地迭代的一件事是引用的位置。循环访问结构数组时,相邻元素在内存中相邻,从而减少了处理器缓存未命中数。
类数组的元素不相邻(当然,对数组中元素的引用是相邻的),这可能会导致在循环访问数组时出现更多的处理器缓存未命中。
另一件需要注意的事情是,结构数组中的连续字节数实际上是(number of elements) * (sizeof(element))
的,而类数组中的连续字节数是(number of elements) * (sizeof(reference))
引用的大小为 32 位或 64 位,具体取决于内存模型。
对于大型结构的大型数组,数组的总大小将超过 2^31 字节,这可能是一个问题。
您可能会在速度上看到的另一个差异是将大型结构作为参数传递时 - 显然,按值传递对堆栈上引用类型的引用副本比按值传递大型结构的副本要快得多。
最后,请注意,您的示例结构不是很有代表性。它包含许多引用类型,所有这些类型都将存储在堆上的某个位置,而不是数组本身。
根据经验,结构的大小不应超过 32 字节左右(确切的限制是一个有争议的问题),它们应该只包含基元(可 blitable)类型,并且它们应该是不可变的。而且,通常,无论如何,您都不必担心使事物成为结构,除非您对它们有可证明的性能需求。
首先,由于数组存储结构,当我尝试使用索引操作从数组访问结构时,我应该获取结构的副本还是对原始结构的引用?
让我告诉你实际发生了什么,而不是回答你措辞混乱的"非此即彼"问题。
- 数组是变量的集合。
- 应用于数组时的索引操作会生成一个变量。
- 成功改变可变结构的字段需要您手头有包含要改变的结构的变量。
所以现在回答你的问题:你应该得到对结构的引用吗?
- 是的,从某种意义上说,变量指的是存储。
- 否,从某种意义上说,变量不包含对对象的引用;结构不装箱。
- 否,从某种意义上说,变量不是
ref
变量。 - 但是,如果对索引器的结果调用了实例方法,则会为您生成一个 ref 变量;该 ref 变量称为"this",并且它将传递给您的实例方法。
你看这是多么令人困惑。最好根本不考虑参考文献。 考虑变量和值。 为数组编制索引会生成一个变量。
现在推断出如果您使用列表而不是数组会发生什么,知道列表的 getter 索引器生成值而不是变量。
在这种情况下,当我在运行代码后检查数组时,我得到了突变的结构值。为什么会这样?
您更改了一个变量。
我得到的时间大致相同。这背后的原因是什么?
差异是如此之小,以至于它被您在这两种情况下所做的所有内存分配和内存副本所淹没。这才是真正的收获。对存储在数组中的可变值类型的操作是否稍微快一些?可能。(它们还节省了收集压力,这通常是更相关的性能指标。但是,尽管相对节省可能很大,但节省占总工作量的百分比通常很小。如果你有性能问题,那么你想攻击最昂贵的东西,而不是已经便宜的东西。