1

我正在开发的 C#/.NET 应用程序使用了巨大的字节数组,并且存在内存碎片问题。使用 CLRMemory 检查内存使用情况

请参考 LOH 和可用空间的图像

我们使用的代码如下

PdfLoadedDocument loadedDocument = new PdfLoadedDocument("myLoadedDocument.pdf");

// Operations on pdf document

using (var stream = new MemoryStream())
{
    loadedDocument.Save(stream);
    loadedDocument.Close(true);
    return stream.ToArray(); //byte[]
}

我们在整个应用程序的多个位置使用类似的代码,我们将其称为循环以生成从几百到 10000 的批量审计

  1. 现在有没有更好的方法来处理这个以避免碎片

作为审计的一部分,我们还使用以下代码从 Amazon S3 下载大文件

using (var client = new AmazonS3Client(_accessKey, _secretKey, _region))
{
   var getObjectRequest = new GetObjectRequest();
   getObjectRequest.BucketName = "bucketName";
   getObjectRequest.Key = "keyName";

   using (var downloadStream = new MemoryStream())
   {
       using (var response = await client.GetObjectAsync(getObjectRequest))
       {
           using (var responseStream = response.ResponseStream)
           {
               await responseStream.CopyToAsync(downloadStream);
           }
           return downloadStream.ToArray(); //byte[]
       }
   }
}
  1. 有没有更好的选择来下载大文件而不将它们移动到 LOH,这对垃圾收集器造成了损失
4

1 回答 1

2

这里有两个不同的东西:

  1. 的内部MemoryStream
  2. 的使用.ToArray()

对于内部发生的事情MemoryStream:它实现为一个简单的,但是您可以通过使用nuget 包byte[]来减轻很多开销,该包在独立使用之间重用缓冲区。RecyclableMemoryStreamMicrosoft.IO.RecyclableMemoryStream

ToArray()坦率地说:不要那样做。使用 vanillaMemoryStream时,更好的方法是TryGetBuffer(...),它为您提供超大的后备缓冲区以及开始/结束标记:

if (!memStream.TryGetBuffer(out var segment))
    throw new InvalidOperationException("Unable to obtain data segment; oops?");
// see segment.Offset, .Count, and .Array

然后,您的工作就是不要超出这些界限。如果您想让这更容易:考虑将段视为跨度(或内存):

ReadOnlySpan<byte> muchSafer = segment;
// now you can't read out of bounds, and you don't need to apply the offset yourself

然而,这种TryGetBuffer(...)方法并不适用RecyclableMemoryStream——因为它会制作防御性副本以防止独立数据出现问题;在这种情况下,您应该将流简单地视为流,即Stream- 只需写入它,倒回它(Position = 0),然后让消费者从中读取,然后在完成后将其处理掉。


附带说明:使用StreamAPI 读取(或写入)时:考虑将数组池用于暂存缓冲区;所以而不是:

var buffer = new byte[1024];
int bytesRead;
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
{...}

而是尝试:

var buffer = ArrayPool<byte>.Shared.Rent(1024);
try
{
    int bytesRead;
    while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
    {...}
}
finally
{
    ArrayPool<byte>.Shared.Return(buffer);
}

在更高级的场景中,使用管道API 而不是API可能是明智的;这里的重点是管道允许不连续的缓冲区,因此即使在处理复杂场景时也不需要大得离谱的缓冲区。然而,这是一个利基 API,在公共 API 中的支持非常有限。

于 2021-03-22T11:50:01.127 回答