C#中的struct数组与对象数组有何区别?

5

我知道可变结构体是有问题的。但是,我仍然想比较一下结构体数组和对象数组的性能。这是我目前所拥有的:

 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()函数内部比较时间时,我获得了大致相同的时间。这背后的原因是什么?是否存在某些情况,其中使用结构体数组或对象数组将优于另一种方式?

谢谢


MSDN的选择类和结构体之间的区别指南。MSDN在底部提供了方便的要点,以便更快地阅读。话虽如此,解决结构体与类之间的最简单方法是使用MSDN的指南,“框架中的大多数类型应该是类”。 - Brian
2个回答

7
访问结构体数组的元素属性时,您不是在操作结构体的副本 - 您在操作结构体本身。 (但是对于 List<SomeStruct> 来说情况并非如此,这将操作副本,并且您示例中的代码甚至不会编译。)
您看到类似时间的原因是因为这些时间被循环中的 (j % 100).ToString()new int[] {1,2,3,4,j%50}; 语句所扭曲。 这两个语句所需的时间使得数组元素访问所需的时间相形见绌。
我稍微修改了测试应用程序,对于访问9.3秒的结构体数组和10秒的类数组(循环1,000,000,000次),因此结构体数组明显更快,但是差别不大。
可以使结构体数组更快的一件事是引用局部性。 当迭代结构体数组时,相邻的元素在内存中也是相邻的,这减少了处理器缓存未命中的数量。
类数组的元素不是相邻的(虽然数组中的元素的引用当然是相邻的),这可能导致在迭代数组时出现更多的处理器缓存未命中。
另一个需要注意的问题是,结构体数组中连续的字节数实际上是 (元素数) * (sizeof(element)),而类数组中连续的字节数为 (元素数) * (sizeof(reference)),其中引用的大小为32位或64位,这取决于内存模型。
当结构体较大且数组较大时,这可能会成为问题,总数组大小将超过2^31字节。
速度上你也可能看到另一个不同之处是在传递大型结构体作为参数时 - 显然,通过值传递引用类型的副本比通过值传递大型结构体的副本要快得多。
最后,请注意您的示例结构体并不是非常具有代表性。 它包含许多引用类型,所有这些引用类型都将存储在堆上,而不是在数组本身中。
粗略估计,结构体的大小不应超过32个字节左右(确切的限制是有争议的),它们应只包含原始(可平面化)类型,并且它们应该是不可变的。 通常情况下,除非您对它们有可证明的性能需求,否则不必担心使事物成为结构体。

谢谢。我想向您澄清一个问题:当您说示例结构不太具有代表性,因为它包含引用类型时,您是否意味着数组将保持该引用的局部性,但是结构中的所有引用类型(例如我的示例中的字符串)都将存储在堆上,并且结构保存指向这些引用类型的指针?如果是这种情况,那么公式(no of elements) * (sizeof(element))将变为(no of value-type elements) * (sizeof(value-type elements)) + (no of reference-type elements) * (sizeof(reference))。这正确吗? - user2635088

7
首先,由于数组存储结构体,当我尝试使用索引操作从数组中访问结构体时,我应该得到结构体的副本还是原始结构体的引用?
让我告诉你实际发生了什么,而不是回答你混淆不清的二选一问题。
- 数组是变量的集合。 - 当应用于数组时,索引操作会产生一个变量。 - 成功地改变可变结构的字段需要手头拥有包含要改变的结构的变量。
现在回答你的问题:你应该得到结构体的引用吗?
- 是,在变量引用存储的意义上。 - 否,因为变量不包含对对象的引用;结构体没有被装箱。 - 否,在变量不是ref变量的意义上。 - 然而,如果你在索引器的结果上调用了一个实例方法,那么一个 ref 变量将被为你生成;这个 ref 变量称为 "this",并且将被传递给你的实例方法。
你看到这有多混乱了。最好根本不考虑引用。考虑变量。索引一个数组会产生一个变量
现在推断一下,如果你使用的是列表而不是数组会发生什么,知道列表的getter索引器产生一个值而不是一个变量。
在运行代码后检查数组时,我获得了改变的结构体值。为什么会这样?
你改变了一个变量。
我得到了大致相同的时间。原因是什么?
差异如此之小,以至于被你在两种情况下所做的所有内存分配和内存复制淹没。这才是真正的要点所在。在可变值类型存储在数组中的操作上速度稍微快一些吗?可能。它们还节省了集合压力,这通常是更重要的性能指标。但尽管相对节省可能很显著,但作为总工作的百分比来说却往往非常小。如果您有性能问题,则需要解决最昂贵的事情,而不是已经便宜的东西。

我想在Eric的回答中再添加一个细节。如果您将数组索引分配给变量(例如,var myStruct = a[index];),并将其更新为myStruct.hello3 = 1234;,则不会在a[index]处更新结构体数组,因为它是在从a[index]检索到的结构体副本上操作的。但是,a[index].hello3 = 1234;可以按预期工作。 - stun

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接