为啥在 c# 中重用数组会显着提高性能?

Posted

技术标签:

【中文标题】为啥在 c# 中重用数组会显着提高性能?【英文标题】:Why does reusing arrays increase performance so significantly in c#?为什么在 c# 中重用数组会显着提高性能? 【发布时间】:2011-03-04 01:09:21 【问题描述】:

在我的代码中,我执行了大量任务,每个任务都需要大量内存来临时存储数据。我有大约 500 个任务。在每个任务开始时,我为 一个数组:

double[] tempDoubleArray = new double[M];

M 是一个很大的数字,取决于精确的任务,通常在 2000000 左右。现在,我做了一些复杂的计算来填充数组,最后我使用数组来确定这个任务的结果。之后, tempDoubleArray 超出范围。

分析表明构造数组的调用非常耗时。因此,我决定尝试重用该数组,将其设为静态并重用它。它需要一些额外的杂耍来 找出数组的最小大小,需要额外通过所有任务,但它有效。现在,程序要快得多(从 80 秒到 22 秒执行所有任务)。

double[] tempDoubleArray = staticDoubleArray;

但是,我有点不明白为什么它的效果如此之好。我说在原始代码中,当 tempDoubleArray 超出范围时,它可以被收集,所以分配一个新数组应该没有那么难吧?

我之所以问这个问题是因为了解它的工作原理可能会帮助我找出实现相同效果的其他方法,并且因为我想知道在什么情况下分配会导致性能问题。

【问题讨论】:

【参考方案1】:

仅仅因为某些东西可以被收集并不意味着它会。事实上,如果垃圾收集器像它的收集器那样激进,你的性能会明显变差。

请记住,创建数组不仅仅是创建一个变量,而是创建N 变量(N 是数组中元素的数量)。重用数组是提高性能的一种物超所值的好方法,但您必须谨慎行事。

澄清一下,我所说的“创建变量”具体是指为它们分配空间并执行运行时必须执行的任何步骤以使它们可用(即将值初始化为零/空)。因为数组是引用类型,所以它们存储在堆上,这使得内存分配变得更加复杂。根据数组的大小(总存储空间是否超过 85KB),它将存储在普通堆或大对象堆中。与所有其他堆对象一样,存储在普通堆上的数组可以触发堆的垃圾收集和压缩(这涉及在当前使用的内存周围进行混洗以最大化连续的可用空间)。存储在大对象堆上的数组不会触发压缩(因为 LOH 永远不会压缩),但它可能会通过占用另一个大的连续内存块来触发过早收集。

【讨论】:

不是创建 N 个变量 - 它只是分配一块内存并将位清零。例如,在数组创建期间不会调用任何构造函数。根据对核心 i7 内存带宽的粗略估计,您预计将这 16MB 归零大约需要 1ms。 @EamonNerbonne:明确地说,我说它是在创建变量(确实如此),而不是 instances 但你必须同意这是一件很奇怪的事情。创建一个变量是什么意思?变量是一个抽象的编译时概念,它在运行时本身不任何事情。 @EamonNerbonne:当然可以。正如您所说,“创建变量”涉及为其分配空间。位是否清零的细节是特定于运行时的实现细节。此外,由于数组存储在堆上(因为它们是引用类型),重复声明和丢弃大型数组可能会对性能产生重大影响,因为它们可能被放置在 LOH 上(取决于大小)并可能导致更早的 GC 操作为了释放世代空间。 归零不是实现细节;这是规范。所有字段(和数组元素)都被指定为零初始化,无论这对类型是否“有意义” - 很难想象一个不清除内存但仍满足规范的实现。但真正困扰我的是试图理解像这样(精度很重要)这样的低级细节的性能特征,然后调用字段和数组元素变量就好像没有区别一样。【参考方案2】:

一个答案可能是large object heap - 大于 85KB 的对象分配在不同的 LOH 上,该 LOH 的收集频率较低且未压缩。

查看性能影响部分

存在分配成本(主要是清除分配的内存) 收集成本(LOH 和 Gen2 一起收集 - 导致 Gen2 中的大型对象压缩)

【讨论】:

【参考方案3】:

在存在碎片的情况下分配大块内存并不总是那么容易。我不能肯定地说,但我的猜测是它必须进行一些重新排列才能为这么大的内存块获得足够的连续内存。至于为什么分配后续数组并不快,我的猜测是要么大块在 GC 时间和下一次分配之间被碎片化,要么原始块从未被 GCd 开始。

【讨论】:

其实由于使用了虚拟内存,分配连续块应该完全没有问题。 我完全不清楚虚拟内存是如何发挥作用的。CLR 不是维护自己的堆吗?如果是这样,那么操作系统级别的内存分页可能只不过是导致不可预测的减速的来源。 它们需要在虚拟地址空间中是连续的,因此仍然存在碎片问题。 但是,对象大小只有 16MB,并且通常一次只有 一个 这样的对象(从它的声音来看),碎片不会成为问题 -除非过程的其他部分导致问题。

以上是关于为啥在 c# 中重用数组会显着提高性能?的主要内容,如果未能解决你的问题,请参考以下文章

WebGL 警告:“属性 0 已禁用。这会显着降低性能”

Windows 7(或更高版本)中活动可见 HWND 的数量是不是会显着影响性能?

C# 提高数组查找循环性能

英特尔® Parallel Studio XE 2013 for Linux* 和 R 的集成是不是会显着提升性能?

通过将长时间运行的任务拆分为单独的进程来提高程序性能

是否有 Eclipse 设置可以显着提高 JavaScript 编辑器在大文件上的性能?