[C#技巧]为什么List<struct>比List<class>快15倍
作者:admin 时间:2023-5-6 17:30:29 浏览:测试发现,C#中 List<Struct>
的分配速度比 List<Class>
快 15 倍,这是一个令人惊诧的结果,但我想知道为什么会这样!
struct比class快15倍
下面是测试代码:
public class PointClass
{
public int X { get; set; }
public int Y { get; set; }
}
public struct PointStruct
{
public int X { get; set; }
public int Y { get; set; }
}
[Benchmark]
public void ListOfClassesTest()
{
const int length = 1000000;
var items = new List<PointClass>(length);
for (int i = 0; i < length; i++)
items.Add(new PointClass() { X = i, Y = i });
}
[Benchmark]
public void ListOfStructsTest()
{
const int length = 1000000;
var items = new List<PointStruct>(length);
for (int i = 0; i < length; i++)
items.Add(new PointStruct() { X = i, Y = i });
}
ListOfStructsTest
和ListOfClassesTest
方法几乎相同。第一个方法分配一百万个PointClass
实例并将它们添加到列表中,而第二个方法分配一百万个PointStruct
实例并将它们也添加到列表中。类型PointClass
和PointStruct
具有相同的成员,但唯一的小而关键的区别是,PointClass
是一个类,而是PointStruct
一个结构。
测试结果非常令人印象深刻:
ListOfStructsTest
方法比ListOfClassesTest
方法快 15 倍以上。
下面我们试着分析一下为什么会出现如此巨大的时差。
在堆上分配引用类型实例和结构实例的区别
对我们来说,第一件事是理解在堆上分配一个引用类型实例和在堆栈上分配一个结构实例之间的区别。
public void Test()
{
var obj = new object(); //引用类型分配
int x = 12; //值类型分配
}
在托管堆中为引用类型分配内存的时间通常是快速操作,对象被连续分配和存储。公共语言运行时具有指向内存中第一个空闲空间的指针,分配新对象涉及将新对象的大小添加到指针。
将对象放在托管堆上后,其地址将写回在堆栈上创建的引用obj
。
总的来说整个过程还是挺便宜的,然而,为引用类型的对象分配内存的过程并不总是那么容易,并且可能涉及额外的繁重部分。
如果引用类型大于 85K 字节,运行时将花费更多时间在大对象堆中寻找合适的位置来存储对象,因为那里的内存是碎片化的(空闲块或地址空间中的“空洞”)。
在小对象堆中没有更多可用空间来存储应用程序请求的对象的情况下,引用类型对象分配很慢。当发生这种情况时,公共语言运行时需要运行垃圾收集过程。如果垃圾收集器没有释放足够的内存,运行时会请求额外的虚拟内存页面。
如何在堆栈上分配一个值类型实例?
为值类型分配内存几乎是即时操作,分配时间几乎不依赖于值类型的大小。运行时唯一应该做的就是创建一个适当大小的堆栈帧来存储值类型和修改堆栈指针。
要点是,将值类型的实例放在堆栈上是快速的,更重要的是,与在堆上分配引用类型对象相比,它在时间上是一个确定性的过程。
实例分析
现在让我们回到我们的例子。
当一个引用类型的一百万个实例被分配时,它们被一个一个地推入托管堆,并且引用被存储回集合实例。实际上,会有100万+1个对象进入内存。
然而,当一个值类型的一百万个实例被分配时,只有一个对象被推入托管堆,它是一个集合的实例。一百万个结构将嵌入到List<T>
实例中。创建实例后,运行时唯一要做的就是用数据填充List<T>
。
在为大型集合选择结构而不是类时,开发人员不仅受益于快速分配时间,还受益于发布时间。
如果开发人员分配了一百万个PointClass
实例,在“标记和清理”阶段,垃圾收集器将不得不扫描一百万个对象并检查每个对象是否还有引用。然后,在“压缩”阶段,垃圾收集器将不得不移动一百万个对象。最终,存储在List<PointClass>
实例中的地址应该更新为新地址。这是很多工作。
但对于垃圾收集器而言,当开发人员分配一百万个List<PointStruct>
实例时情况会好得多,因为垃圾收集器必须使用托管堆中的唯一实例PointStruct
。
总结
结构(struct)可以比类(class)更高效,但在用struct
或其他方式替换class
关键字之前,请始终仔细分析你的具体情况。程序员的工作不是盲目地遵循建议或最佳实践,而是选择最合适的工具、方法、方式来以最佳方式解决各自的独特案例。
相关文章
- [C#技巧]指定List集合容量,速度提升2倍
- [C#技巧]C#进行空(null)检查的正确方法:用is代替==
- [C#技巧]C#数组矩阵for迭代,顺序不同竟然速度相差15倍
- [C#技巧]C#返回空集合的正确方法是使用Array.Empty而不是null
- [C#技巧]C#异常when过滤器捕捉异常使用多个catch的示例代码
- [C#技巧]C#重新抛出异常的正确方法,注意不是throw e!
- [C#技巧]从C#到C# 8、C# 9,简化switch语句越来越惊艳
- [C#技巧]使用using块比没有using块语句消耗更少的内存
- [C#技巧]使用StringBuilder比String消耗内存少
- [C#技巧]new运算符使代码执行速度慢了5倍
- [C#技巧]在for循环外比在for循环内使用try-catch速度快
- 11个提高C#代码性能的技巧
- OutputCache缓存优化asp.net代码 提高网页性能
- 站长推荐