【问题标题】:byte[] and efficiently passing by referencebyte[] 并通过引用有效地传递
【发布时间】:2012-01-31 15:45:27
【问题描述】:

因此,这与处理大对象堆和尝试最小化实例化 byte[] 的次数有关。基本上,我遇到了 OutOfMemoryExceptions,我觉得这是因为我们实例化了太多字节数组。当我们处理几个文件时,该程序运行良好,但它需要扩展,目前还不能。

简而言之,我有一个从数据库中提取文档的循环。目前,它一次提取一个文档,然后处理该文档。文档的范围可以从不到 1 兆到 400 多兆。 (因此我一次处理一个)。以下是伪代码,在我优化之前。

所以我正在做的步骤是:

  1. 调用数据库以找到最大的文件大小(然后将其乘以 1.1)

    var maxDataSize = new BiztalkBinariesData().GetMaxFileSize();
    maxDataSize = (maxDataSize != null && maxDataSize > 0)
        ? (long)(maxDataSize * 1.1)
        : 0;
    var FileToProcess = new byte[maxDataSize];
    
  2. 然后我进行另一个数据库调用,从数据库中提取所有文档(不含数据)并将它们放入 IEnumerable。

    UnprocessedDocuments =
        claimDocumentData.Select(StatusCodes.CurrentStatus.WaitingToBeProcessed);
    foreach (var currentDocument in UnprocessDocuments)
    {
         // all of the following code goes here
    }
    
  3. 然后我从外部源填充我的 byte[] 数组:

    FileToProcess = new BiztalkBinariesData()
        .Get(currentDocument.SubmissionSetId, currentDocument.FullFileName);
    
  4. 问题来了。将 currentDocument (IClaimDocument) 传递给其他方法进行处理会更干净。那么如果我将 currentDocument 的数据部分设置为预先格式化的数组,这会使用现有的引用吗?或者这会在大对象堆中创建一个新数组吗?

    currentDocument.Data = FileToProcess;
    
  5. 在循环结束时,我会清除 FileToProcess

    Array.Clear(FileToProcess, 0, FileToProcess.length);
    

说清楚了吗?如果没有,我会尝试清理它。

【问题讨论】:

  • 不要用感情来找出罪魁祸首。使用内存分析器。
  • UnprocessedDocuments或其项目实现IDisposable
  • 在哪里你得到OutOfMemoryException?是否有一个相对一致的地方抛出这个异常,或者看起来是随机的?
  • UnprocessDocuments is IEnumerable(IClaimDocument 是一个数据对象(字符串、整数等)。
  • @Cyfer13 - 我想提一下,这是我多年来见过的最好的伪代码。您的问题非常清楚,您提供了足够的信息来理解您所理解的内容,并提供了伪代码,虽然并不完美地解释了您遇到的问题。如果我能给你第二次投票,我会的。

标签: c# asp.net c#-3.0 bytearray out-of-memory


【解决方案1】:

第 1 步:

var FileToProcess = new byte[maxDataSize];

第 3 步:

FileToProcess = new BiztalkBinariesData()
    .Get(currentDocument.SubmissionSetId, currentDocument.FullFileName);

您的步骤 1 完全没有必要,因为您在步骤 3 中重新分配了数组 - 您正在创建一个 new 数组,您不会填充现有数组 - 所以基本上步骤 1 只是创建GC 需要做更多的工作,如果您按顺序执行(如果编译器没有对其进行优化,这完全有可能)可能会解释您所看到的一些内存压力。

【讨论】:

  • 正要自己发布这个。此外,行“Array.Clear(FileToProcess, 0, FileToProcess.length);”没有清除任何东西。它不会释放任何内存,它只是将值设置为 0(但仍分配)。它只会做(相当多)无用的工作。删除此行。
  • @Cyfer13 是的,它每次都在创建一个新的字节数组,这就是问题所在。如果您重新使用在开始时创建的相同数组,它不会占用超过该数量的内存。数组本身是在数据库的执行方法中分配的。
  • 不,在那里添加演员表不会改变任何事情。我怀疑您是否有任何方法可以更改方法以便能够重新使用数组。这就是为什么我们一直说创建一个新数组是没有意义的;您无法阻止数据库适配器创建自己的数组。当您说“FileToProcess = ...”时,您并未使用右侧数组中的数据填充 FileToProcess 数组。 FileToProces 只是指向一个数组,并且您正在将它指向的数组从它之前的任何内容更改为 Execute 方法返回的新数组。 (...)
  • (续)每次将新数组分配给 FileToProcess 时,前一个数组就会被丢弃(因为您没有在其他地方保留它)。当您将 FileToProcess 分配给第一个文件时,您将丢弃您在开始时创建的数组。当您分配第二个文件时,您将丢弃第一个文件中的数组。一旦数组不再被引用,垃圾收集器最终会出现并释放该内存,但这只会偶尔发生。由于您不断“丢弃”大量大型数组,因此会占用大量内存。
  • 虽然我与 SqlDataReader 的合作不多,但它看起来正在做你希望它在这里做的事情,是的。您还应该按照那里的模型填充(相当小的)缓冲区并循环,直到您浏览完整个文件,而不是尝试一次获取整个文件的字节。这将显着缩小程序的内存占用,无需分配 500MB 字节数组。
【解决方案2】:

数组是引用类型,因此您将传递引用的副本,而不是数组本身的副本。这仅适用于 值类型

这个简单的 sn-p 说明了数组作为引用类型的行为:

public void Test()
{    
    var intArray = new[] {1, 2, 3, 4};
    EditArray(intArray);
    Console.WriteLine(intArray[0].ToString()); //output will be 0
}

public void EditArray(int[] intArray)
{
    intArray[0] = 0;
}

【讨论】:

  • 您所描述的只有在不使用 ref 的情况下才是正确的。不清楚是不是这样,示例代码只是试图解释问题,多年来我见过的最好的伪代码。
  • @Ramhound: ref 不会改变数组通过引用传递的事实。唯一的区别是引用是否作为副本传递。我在这篇文章中描述的行为是真实的,不管是不是ref
【解决方案3】:

它会使用现有的引用,不用担心。数组的内容不会被复制。

【讨论】:

  • 我认为这应该是一条评论
  • @Henk Holterman:好的,没有异议,但我明白问题是为什么 OP 面临 OOM 异常
  • @sll:在 #4 下它说“这是问题”,所以我回答了它:)
  • 欣赏它。不幸的是,我发现我问错了问题。
【解决方案4】:

您的问题可能在于“BiztalkBinariesData”类的实现和使用。

我不确定它是如何实现的,但我确实看到你每次都在声明一个新实例

new BiztalkBinariesData()

想一想..

【讨论】:

  • 这是伪代码。我一般实现对象一次,然后在循环中调用Get()。
  • 正如在另一篇文章中向我指出的那样,您是正确的。 Get() 方法当前正在实例化一个新对象,而不是使用预先存在的引用。
【解决方案5】:

我遇到了 OutOfMemoryExceptions,我觉得这是因为我们实例化了太多字节 数组的

不,这是因为你分配了 LARGE 数组。将它们限制为 48kb 或 64kb 并将它们与自定义容器“组合”。 64kb 意味着您可以取索引的较高 2 个字节来确定要使用的数组。该容器包含一个数组数组。处理非常大的对象会导致碎片化,并且以后无法分配一个大数组。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-05-09
    • 2015-11-26
    • 1970-01-01
    • 1970-01-01
    • 2012-07-09
    • 2013-03-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多