为什么在c#中重用阵列会显著提高性能?

为什么在c#中重用阵列会显著提高性能?,c#,arrays,garbage-collection,reusability,C#,Arrays,Garbage Collection,Reusability,在我的代码中,我执行大量任务,每个任务都需要大量内存来临时存储数据。我有大约500项任务。在每个任务开始时,我为每个任务分配内存 数组: double[] tempDoubleArray = new double[M]; M是一个很大的数字,取决于精确的任务,通常在2000000左右。现在,我做了一些复杂的计算来填充数组,最后我使用数组来确定这个任务的结果。之后,tempDoubleArray将超出范围 分析表明,构造数组的调用非常耗时。因此,我决定尝试重用该数组,将其设置为静态并重用。这需要

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

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

分析表明,构造数组的调用非常耗时。因此,我决定尝试重用该数组,将其设置为静态并重用。这需要一些额外的杂耍来完成 计算出数组的最小大小,需要额外遍历所有任务,但它可以工作。现在,程序速度要快得多(所有任务的执行时间从80秒提高到22秒)

然而,我有点搞不清楚为什么这个方法这么有效。我想说,在原始代码中,当tempDoubleArray超出范围时,它可以被收集,所以分配一个新数组应该不会那么困难,对吗

我问这个问题是因为理解它为什么会起作用可能会帮助我找到实现同样效果的其他方法,因为我想知道在什么情况下分配会带来性能问题。

仅仅因为可以收集某些东西并不意味着它会。事实上,如果垃圾回收器像其回收中的垃圾回收器那样具有攻击性,那么您的性能将显著下降

请记住,创建数组不仅仅是创建一个变量,而是创建
N
变量(
N
是数组中的元素数)。重用阵列是提高性能的一个好方法,尽管您必须谨慎地这样做

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

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

请参阅有关性能影响的部分

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

    • 在存在碎片的情况下分配大块内存并不总是那么容易。我不能肯定,但我的猜测是,它必须进行一些重新排列,以获得足够的连续内存来容纳如此大的内存块。至于为什么分配后续阵列的速度不快,我的猜测是,要么是在GC时间和下一次分配之间,大数据块变得支离破碎,要么是原始数据块从一开始就不是GCd。

      实际上是由于使用了虚拟内存,分配连续的块应该一点也不成问题。我一点也不清楚虚拟内存是如何工作的。CLR不维护自己的堆吗?如果是这样,那么操作系统级内存分页可能只会导致不可预测的速度减慢。它们需要在虚拟地址空间中连续,因此碎片问题仍然存在。但是,由于对象大小仅为16MB,通常一次只有一个这样的对象处于活动状态(听上去),碎片化不会是一个问题——除非进程的其他部分导致问题。它不是创建N个变量——它只是分配一块内存并将位归零。例如,在数组创建过程中从来没有调用过构造函数。根据对core i7内存带宽的粗略估计,你可能会认为16MB的内存带宽为1ms左右。@Eamonnerbonen:说得很清楚,我说的是它在创建变量,而不是实例。但你必须同意这是一个奇怪的说法。创建一个变量意味着什么?变量是一个抽象的编译时概念,在运行时它本身不会做任何事情。@eamonnerbanne:当然会。正如您所说,“创建变量”涉及为其分配空间。位是否清零的细节是特定于运行时的实现细节。此外,由于数组存储在堆上(因为它们是引用类型),重复声明和丢弃大型数组可能会对性能产生重大影响,因为它们可能被放置在LOH上(取决于大小),并可能导致较早的GC操作以释放代空间;这是规范。所有字段(和数组元素)都被指定为零初始化,无论这是否“合理”对于类型或类型-很难想象一个实现不会清除内存,但仍然满足规范。但真正困扰我的是试图理解像这样的低级细节的性能特征(精度很重要),然后调用字段和数组元素变量,就好像没有区别一样。
      double[] tempDoubleArray = staticDoubleArray;