繁体   English   中英

Memory C# 中的 byte[] 分片

[英]Memory Fragmentation with byte[] in C#

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

请参考 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,这对垃圾收集器造成了损失

这里有两个不同的东西:

  1. MemoryStream的内部结构
  2. .ToArray()的用法

For what happens inside MemoryStream : it is implemented as a simple byte[] , but you can mitigate a lot of the overhead of that by using RecyclableMemoryStream instead via the Microsoft.IO.RecyclableMemoryStream nuget package, which re-uses buffers between independent usages.

对于ToArray() ,坦率地说:不要那样做 使用 vanilla MemoryStream时,更好的方法是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配合使用——因为它会生成防御性副本以防止独立数据出现问题; in that scenario, you should treat the stream simply as a stream , ie Stream - just write to it, rewind it ( Position = 0 ), and have the consumer read from it, then dispose it when they are done.


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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM