如何解决在 C# 中使用 byte[] 进行内存碎片
我正在开发的 C#/.NET 应用程序使用了巨大的字节数组并且存在内存碎片问题。使用 CLRMemory 检查内存使用情况
我们使用的代码如下
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 个不等的批量审计
- 现在有更好的方法来避免碎片化
作为审计的一部分,我们还使用以下代码从 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[]
}
}
}
解决方法
这里有两件不同的事情:
-
MemoryStream
的内部结构 -
.ToArray()
的用法
对于 MemoryStream
内部发生的事情:它是实现为一个简单的 byte[]
,但是您可以通过使用 RecyclableMemoryStream
来减轻很多开销而是通过 Microsoft.IO.RecyclableMemoryStream
nuget 包,它在独立使用之间重用缓冲区。
对于ToArray()
,坦率地说:不要那样做。使用原版 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
- 因为它制作了防御性副本以防止独立数据出现问题;在那个场景中,您应该简单地将流视为流,即Stream
- 只需写入它,倒带它(Position = 0
),并让消费者从中读取,然后在完成后将其丢弃。
附带说明:使用 Stream
API 读取(或写入)时:考虑使用数组池作为暂存缓冲区;所以而不是:
var buffer = new byte[1024];
int bytesRead;
while ((bytesRead = stream.Read(buffer,buffer.Length)) > 0)
{...}
改为尝试:
var buffer = ArrayPool<byte>.Shared.Rent(1024);
try
{
int bytesRead;
while ((bytesRead = stream.Read(buffer,buffer.Length)) > 0)
{...}
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
在更高级的场景中,使用 pipelines API 而不是 stream API 可能更明智;这里的重点是管道允许不连续缓冲区,因此即使在处理复杂场景时,您也永远不需要大得离谱的缓冲区。然而,这是一个小众 API,并且在公共 API 中的支持非常有限。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。