内存使用和操作图像
Posted
技术标签:
【中文标题】内存使用和操作图像【英文标题】:Memory usage and manipulating images 【发布时间】:2020-02-17 13:00:15 【问题描述】:TL;DR;转换为 base64string 的图像在大型对象堆中占用大量 RAM。
我在 Windows 服务中有一些代码,它使用用户上传的我们的产品图像,将它们标准化为网络级格式(他们将上传 10MB 位图),并执行一些其他操作,例如将它们调整为正方形并添加空格填充。
然后它将它们转换为 base64 字符串,以通过 rest 将它们上传到我们的托管环境中。环境要求这样做,我不能使用 URLS。当我这样做时,它们会存储在大型对象堆中,并且随着时间的推移,程序的 RAM 使用量会猛增。
我该如何解决这个问题?
这里是代码,
private void HandleDocuments(IBaseProduct netforumProduct, MagentoClient client, bool isChild)
if (netforumProduct.Documents == null) return;
for (int idx = 0; idx < netforumProduct.Documents.Count; idx++)
JToken document = netforumProduct.Documents[idx]["Document"];
if (document == null) continue;
string fileName = document["URL"].ToString();
// Skip photos on child products (the only identifier is part of the url string)
if (fileName.ToLower().Contains("photo") && isChild) continue;
using (HttpClient instance = new HttpClient BaseAddress = client.NetforumFilesBaseAddress)
string trimStart = fileName.TrimStart('.');
string base64String;
using (Stream originalImageStream = instance.GetStreamAsync("iweb" + trimStart).Result)
using (MemoryStream newMemoryStream = new MemoryStream())
using (Image img = Image.FromStream(originalImageStream))
using (Image retImg = Utility.Framework.ImageToFixedSize(img, 1200, 1200))
retImg.Save(newMemoryStream, ImageFormat.Jpeg);
newMemoryStream.Position = 0;
byte[] bytes = newMemoryStream.ToArray();
base64String = Convert.ToBase64String(bytes);
// MediaGalleryEntry is a simple class with a few string properties
MediaGalleryEntry mge = new MediaGalleryEntry
label = "Product_" + netforumProduct.Code + "_image_" + idx,
content = new MediaGalleryContent
base64_encoded_data = base64String,
name = "Gallery_Image_" + idx
,
file = trimStart
;
this.media_gallery_entries.Add(mge);
它不是有史以来最好的代码,可能没有高度优化,但它是我能做到的最好的。
【问题讨论】:
好吧,一个 10 MB 的位图可能会变成一个 1 MB 的 JPEG,而后者又会变成一个 1.3 MB 的 base64 字符串。在this.media_gallery_entries.Add(mge)
,您保留对此字符串的引用,因此它不能被垃圾回收。这是你的问题吗?
是的,这正是问题所在。一旦我发布到网络服务,我真的不确定如何处理这个字符串。
@CarComp 只需停止引用它,GC 就会最终收集它。不需要特殊处理,因为它只是一个字符串。
根据您构建 Web 请求的方式,构建文件(即通过流式传输,而不是通过构建字符串然后写入)并上传它可能会绕过这个(甚至绕过文件并直接流式传输到 URL,但这可能会更复杂,具体取决于 API)。这意味着不使用Convert
,而是使用支持流like ToBase64Transform
的东西。
您可能是指提供(或生成)的调用 REST API 的编程 API 可与字符串一起使用,但这可能并不意味着不可能编写没有此缺陷的新 API。 REST 的全部特点是它很简单,因此任何语言都可以使用 API。最终,这一切都以 TCP 上的字节流的形式结束,因此在 C# 中仅从物理端点构造大字符串没有(不可能)硬性要求。它可能就像给MediaGalleryEntry
一个Stream
类型的属性一样简单。
【参考方案1】:
TL;DR;转换为 base64string 的图像在大型对象堆中占用大量 RAM
是的,这显然是真的。所有图像都很大。压缩方法仅适用于存储和传输。但是当图像被加载到内存中 - 用于显示或进一步处理 - 所有压缩步骤必须被撤消。这是与他们一起工作的人的常见陷阱。
然后它将它们转换为 Base64 字符串,以通过 rest 将它们上传到我们的托管环境中。环境要求这样做,我不能使用 URLS。当我这样做时,它们会存储在大型对象堆中,并且随着时间的推移,程序的 RAM 使用量会猛增。” Base64 是无效的,但不会增加很多。 +25% IIRC。
如果您真的在这里看到了问题,或者只是误读了内存占用,那么大问题? @CodeCaster 发现您保留了一个引用(这是一个真正的问题,也是您在 .NET 中完全可以获取内存泄漏的少数几种方法之一),但即使您松开这些,该字符串仍会在内存中保留一段时间.
.NET 使用 GarbageCollection 内存管理方法。这种方法有一个问题:当 GC 收集时,访问同一托管区域的所有其他线程都必须暂停。结果,GC - 由于缺乏更好的术语 - 在运行时非常惰性。如果它只在应用程序关闭时运行一次,那是理想的情况。唯一能让它更早运行的是:
对GC.Collect();
的调用通常不应用于生产代码,仅用于在遇到参考内存泄漏时进行调试
OOM 预期的危险
一些替代的 GC 模式,特别是像服务器模式这样的东西
我只能告诉你它会运行最终。但我认为您不一定需要知道确切的时间。
【讨论】:
是的,这基本上总结了。这样做是一个固有的缺点。处理图像使用 ram,没有万能的解决方案。以上是关于内存使用和操作图像的主要内容,如果未能解决你的问题,请参考以下文章
Python 和 Django - 如何在内存和临时文件中使用