字节数组和通过引用高效传递

5

这段内容涉及到处理大对象堆(Large Object Heap),并尝试最小化实例化byte[]的次数。我遇到了OutOfMemoryExceptions异常,感觉是因为我们实例化了过多的byte数组。当我们处理几个文件时,程序可以正常运行,但需要扩展规模,目前还无法做到。

简而言之,我有一个循环从数据库中提取文档。目前,它一次只提取一个文档,然后处理该文档。文档的大小可以从不到1MB到超过400MB不等。(因此我一次只处理一个)。以下是伪代码,在我进行优化之前。

所以我正在执行以下步骤:

  1. Make a call to the database to find the largest file size (and then multiplying it by 1.1)

    var maxDataSize = new BiztalkBinariesData().GetMaxFileSize();
    maxDataSize = (maxDataSize != null && maxDataSize > 0)
        ? (long)(maxDataSize * 1.1)
        : 0;
    var FileToProcess = new byte[maxDataSize];
    
  2. Then I make another database call pulling all of the documents (without data) from the database and place these into an IEnumerable.

    UnprocessedDocuments =
        claimDocumentData.Select(StatusCodes.CurrentStatus.WaitingToBeProcessed);
    foreach (var currentDocument in UnprocessDocuments)
    {
         // all of the following code goes here
    }
    
  3. Then I populate my byte[] array from an external source:

    FileToProcess = new BiztalkBinariesData()
        .Get(currentDocument.SubmissionSetId, currentDocument.FullFileName);
    
  4. Here is the question. It would be much cleaner to pass the currentDocument (IClaimDocument) to other methods to process. So if I set the data part of the currentDocument to the pre-formatted array, will this use the existing reference? Or does this create a new array in the Large Object Heap?

    currentDocument.Data = FileToProcess;
    
  5. At the end of the loop, I would then clear FileToProcess

    Array.Clear(FileToProcess, 0, FileToProcess.length);
    
清楚吗?如果不清楚,我会尽力简化。

13
不要使用感觉来找出罪犯,而是使用内存分析器。 - Oded
UnprocessedDocuments 或其项是否实现了 IDisposable 接口? - sll
1
你在哪里遇到了 OutOfMemoryException 异常?这个异常是否在一个相对固定的位置被抛出,还是看起来是随机的? - Branko Dimitrijevic
UnprocessDocuments 是 IEnumerable<IClaimDocument>(IClaimDocument 是一个数据对象(字符串,整数等)。 - Cyfer13
1
@Cyfer13 - 我想要提到这是我多年来看到的最好的伪代码。你的问题非常清晰,提供了足够的信息来理解你所理解的内容,并提供了伪代码,虽然不完美,但解释了你遇到的问题。如果我能给你第二个赞同票,我一定会的。 - Security Hound
显示剩余5条评论
5个回答

6

步骤 1:

var FileToProcess = new byte[maxDataSize];

步骤三:

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

第一步是完全不必要的,因为在第三步中重新分配了数组 - 您正在创建一个的数组,而不是填充现有的数组 - 因此,从本质上讲,步骤1只是给GC增加了更多的工作量,如果您快速执行它(如果编译器没有将其优化掉,这是完全可能的),那么这可能解释了您所看到的某些内存压力。


2
我正准备自己发布这个。此外,“Array.Clear(FileToProcess,0,FileToProcess.length);”这一行并没有清除任何东西。它不会释放任何内存,只是将值设置为0(但仍然分配)。它只是在做(相当多的)无用功。删除此行。 - Servy
2
@Cyfer13 是的,它每次都创建一个新的字节数组,这就是问题出现的地方。如果您在开始时重复使用相同的数组,它永远不会占用超过该内存量。数组本身是在数据库的执行方法中分配的。 - Servy
1
(续)每次将新数组分配给FileToProcess时,以前的数组都会被丢弃(因为你没有在其他地方保留它)。当你用第一个文件分配FileToProcess时,你正在丢弃在开始创建的数组。当你分配第二个文件时,你正在丢弃第一个文件中的数组。一旦数组不再被引用,垃圾收集器最终会释放那些内存,但这只会偶尔发生。由于你不断地“丢弃”大量的大型数组,它正在消耗大量的内存。 - Servy
1
到目前为止,你没有提到的一件事情(理所当然)是,一旦你将数据转换成字节数组后,你要用它做什么。这里的主要问题是,“它可以分块处理吗,还是需要全部放在一个大数组中。”如果你不知道,我们可以帮你回答这个问题。能够使用块会更好。想法是,你会读取文件的1MB,处理它,读取下一个1MB,处理它,完成该文件后,读取下一个文件的1MB,以此类推。(重复使用相同的1MB字节数组。)你提供的链接展示了如何获取数据块。(...) - Servy
1
(续)如果您无法使用块并且需要一个大的字节数组,则仍然不需要清除它。除了数组之外,您还应该保留文件的实际长度。有了这个,您就知道该索引之后的所有内容都是垃圾(即来自先前文件的剩余部分),您不应该使用它。这也意味着您不会在每个文件末尾处理一堆空字节。 - Servy
显示剩余10条评论

5

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

这个简单的片段说明了数组作为引用类型的行为:

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,你所描述的情况才是正确的。但是现在并不清楚是否使用了ref,示例代码只是试图解释问题,是我多年来看到的最好的伪代码。 - Security Hound
@Ramhound:ref并不改变数组按引用传递这个事实。唯一的区别在于是否将引用作为副本传递。我在这篇帖子中描述的行为,无论使用ref与否都是正确的。 - InBetween

2

你的问题可能出现在“BiztalkBinariesData”类的实现和使用方面。

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

new BiztalkBinariesData()

有些事情需要考虑..


这是伪代码。我通常只实现一次对象,然后在循环中调用Get()。 - Cyfer13
正如在另一篇帖子中指出的那样,你是正确的。Get()方法目前正在实例化一个新对象,而不是使用预先存在的引用。 - Cyfer13

2

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


1
我认为这应该是一条注释。 - sll
@Henk Holterman:好的,没有异议,但我理解问题是为什么OP遇到了OOM异常。 - sll
@sll:在第4点下面写着“这是问题”,所以我回答了它 :) - Ry-
很抱歉,我发现我提出了错误的问题。感激不尽。 - Cyfer13

1
我遇到了OutOfMemoryExceptions的问题,感觉是因为我们实例化了太多的字节数组。
不,这是因为你分配了大型数组。将它们限制在48kb或64kb左右,并使用自定义容器“合并”它们。64kb意味着您可以使用索引的高2个字节来确定要使用的数组。容器包含一个数组的数组。处理非常大的对象会导致碎片化和后期无法分配一个大型数组。

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