内存使用和操作图像

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,没有万能的解决方案。

以上是关于内存使用和操作图像的主要内容,如果未能解决你的问题,请参考以下文章

绘制应用程序内存占用随时间变化的图像显示

使用 ATL CImage 从内存缓冲区加载图像

Python 和 Django - 如何在内存和临时文件中使用

如何获取PNG图片某点像素的rgba值

允许在 PHP 中裁剪和调整图像大小而不使用太多内存的图像功能 [关闭]

将图像旋转 90 度的算法? (没有额外的内存)