在加载和释放NSImage时,内存持续增加 [英] Memory Continues to Increase when Loading and Releasing NSImage

查看:576
本文介绍了在加载和释放NSImage时,内存持续增加的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个问题,其中我的应用程序积极地消耗内存到颠簸点连续的图像文件加载。例如,考虑以下代码,它重复加载和释放15MB的JPEG文件(用于测试目的的大文件大小):

  NSURL * inputUrl = [NSURL URLWithString:@file:///Users/me/Desktop/15MBjpeg.jpg]; 
for(int i = 0; i <1000; i ++){
NSImage * image = [[NSImage alloc] initWithContentsOfURL:inputUrl];
[image release];
}

由于有大量的空闲系统内存,但最终系统陷入了我所谓的颠簸点。在这里,我相信系统只释放足够的内存来加载下一张图片,因此性能最终会变慢。此外,现在其他应用程序运行缓慢,因为系统必须释放这个囤积,但现在未使用的内存。



对我来说有意义的是,如果它将分配内存,然后让系统释放它,使我的活动监视器中的实时记忆统计量保持小,而不是通过连续加载/释放迭代进入千兆字节。实际上,我可能永远不会在这一点结束,但是当任何时候所需的实际驻留内存通常很小时,我的活动监视器实时内存统计数据最终超过所有其他应用程序似乎奇怪。我原以为这是一些内存泄漏或缓存问题,但它似乎更多的相关的应用程序和惰性内存释放系统上的积极内存分配(不是有任何错误,如果操作系统有这个政策 - 如果这是事实上它的工作方式)。



有没有更好的方法重复加载图像,而没有这种内存占用和不主动缓解内存使用行为?也许有一种方法可以强制应用程序内存占用下降,或者有一种方式更聪明地如何将图像加载到相同的对象或内存位置?我的目标是加载图像,处理它(获取缩略图,更改图像格式等),摆脱它在内存中,然后再做 - 所有没有这种观察到的内存增长。



-



后续:



Bavarious,谢谢。加载同一文件时,NSAutoreleasePool包装会解析迭代内存增长。

  NSURL * inputUrl = [NSURL URLWithString:@file :///Users/me/Desktop/15MBjpeg.jpg]; 
for(int i = 0; i <1000; i ++){
NSAutoreleasePool * apool = [[NSAutoreleasePool alloc] init];
NSImage * image = [[NSImage alloc] initWithContentsOfURL:inputUrl];
[image release];
[apool drain];
}

但是,它不能解决图像后内存保持增加的问题被释放(并且NSAutoreleasePool耗尽)。例如,当加载我的15MB JPEG图像时,Real Mem内存从8MB稳定状态跳转到大约25MB,然后保留在那里。 (我的应用程序只有一个Interface Builder按钮,它有一个有线IBAction的方法,只调用我的复制的for循环)。我希望在for循环完成后(或者如果只有一个映像被加载和释放),实时内存统计将减少回到标称应用程序级内存使用。



在调用NSImage功能时可以加载后台中的其他东西似乎是合理的,这可以增加内存。然而,不同大小的图像(15MB,30MB,50MB等)在应用程序中成比例地增加了内存,这使我相信它比这样的分配更多。



我尝试连续加载单独的图像(例如,15MBjpeg-1.jpg,15MBjpeg-2.jpg等)每个新图像加载的有时存储器。例如,如果连续加载两个图像,则根据我的观察,加载/释放后应用程序的实时内存内存使用量现在大约为50MB,而且不会减少。加载后续图像时,此行为会继续,以便应用程序在加载几个大图像后可以爬入数百MB的实时内存内存使用。



有趣的是,如果我重新加载相同的图像一遍又一遍,稳态存储器不增加。这是否表示某种缓存正在进行?再次,我的目标是批量处理通过几个不同的图像文件,而没有内存中的这种增长。非常感谢。



哦,我正在查看Heapshot Analysis文章的自动取款机,但至少要发布我的进度,看看是否有其他输入。 p>

-



FOLLOW-UP#2



bbum,感谢伟大的文章。我用我的测试程序运行仪器分配,没有找到任何堆增长。在引用的博客文章,我的方法是,1)点击我的加载和释放图像按钮在我的界面生成器界面(调用加载/释放行为),2)点击标记堆几次每几秒钟使用这种方法,Heapshots一致地在堆累积列中报告了0字节(3次点击),然后3)重复1)和2)。



<每5秒15秒),这意味着没有从基线Heapshot分配额外的内存。此外,在统计信息窗格中,每次单击加载和释放映像按钮时,Malloc为13.25MB,但Live Bytes为0字节,表示已完全释放。如果我单击加载和释放映像按钮三次,图像的总字节报告39.75MB(3 * 13.25MB),意味着分配了39.75MB,但是从实时字节为0完全释放。分配图尖峰快速上升,并立即回落,因为这是一个相当快的操作。这似乎是有道理的,没有泄漏和稳定状态使用的内存没有增长。



但是,现在我该怎么做,我的真实内存统计仍然很高?我知道活动监视器不是调试内存问题的标准。但是,Real Mem仍然保持高,直到我关闭程序,然后Real Mem都回到了Free类别,这对我来说是奇怪的。



我用两个图像(15MBjpeg-1.jpg,15MBjpeg-2.jpg)测试了这个方法,只需用相同的方法复制代码,我再也观察不到堆增长。显然,已经做出和发布了更多的分配。然而,现在的真实存储大约是加载和释放一个图像的情况下的两倍。再次,它不会在测试程序的稳定状态下减少。



还有什么我可以做吗?这是一个单一的图像加载/释放的测试代码,任何人谁想要尝试(只需连接一个IB按钮到openFiles):

  #importTestMemory.h//只声明 - (IBAction)openFiles:(id)sender; 
@implementation TestMemory
- (IBAction)openFiles:(id)sender {
NSURL * inputUrl = [NSURL URLWithString:@file:///Users/me/Desktop/15MBjpeg.jpg ];
NSAutoreleasePool * apool = [[NSAutoreleasePool alloc] init];
NSImage * image = [[NSImage alloc] initWithContentsOfURL:inputUrl];
[image release];
[apool drain];
}
@end

感谢阅读。 :)



-



FOLLOW-UP#3

bbum,没有我没有启用垃圾收集(GC在项目设置中设置为不支持)。我使用vmmap和仪器分配中的VM跟踪器栏来调查内存。我假定你是指VM Tracker数据,当你说VM Instruments,因为它报告与vmmap相同的信息。使用我的加载和释放映像按钮打开单个映像后,一些重要的内存号包括以下内容(来自VM Tracker):



;          %ofRes   ResSize   VirtSize Res%  %AllDirty  DirtySize☐
__ TEXT        38%      33.84MB  80.45MB  42%     0%          0Bytesven
* Dirty *       32%      28.23MB   114.99MB  24%    100%         17.11MB 80+
MALLOC_LARGE 14%     ;  13.25MB   13.25MB   100%   0%         4KB├
Carbon        11%      9.86MB    9.86MB   ; 100%   20%         3.46MB

VM_ALLOCATE  9%       8.43MB    48.17MB   18%    49%      8.43MB />
...



有趣的是,单个映像的后续加载/ Dirty 和VM_ALLOCATE类型的常驻大小,大约.3MB,而且这些类型的脏大小也随时间增加。 (VM_ALLOCATE似乎是 Dirty 的子集)。没有其他类型随后的加载/释放而改变。



我不确定从这些数据中带走什么,或者如何使用它来使程序发布记忆。看起来像VM_ALLOCATE类型可能是未被释放的块,但这只是推测。是否可能的底层NSImage init实现的一些部分保存图像文件的缓存,并且不会释放它?同样,如前所述,它使我注意到,相同的图像文件的每个后续加载/释放几乎不消耗与第一加载/释放相比的资源(CPU,盘研磨等)和挂钟时间。提前感谢。

解决方案


  • What Bavarious说:你试过用 NSAutoreleasePool 包围它。


  • 这是一个经典的微基准测试。虽然它确实表明一个问题,但问题可能实际上是基准与预期的现实世界模式有太大的差异,以至于错误在于基准。


  • 在有效设计的应用程序中,它不会从磁盘多次读取相同的图像数据。


  • 这是 Heapshot analysis







<



您描述的症状听起来像是虚拟机泄漏(某些东西耗费地址而不进行分配;映射内存,例如)或者未修剪的缓存(包含虚拟机分配)。




  • 您启用了GC吗?如果是这样,这很容易是因为GC阈值没有被触发。收集者不知道从非GC到GC区域的实际大分配。


  • 尝试查看VM仪器或在命令行中使用vm_map来查看请查看您应用中消耗地址空间的方法。



I have a problem where my application aggressively consumes memory to a "thrashing point" with successive image file loads. For example, consider the following code, which repeatedly loads and releases a 15MB JPEG file (large file size for test purposes):

NSURL *inputUrl = [NSURL URLWithString:@"file:///Users/me/Desktop/15MBjpeg.jpg"];  
for(int i=0; i<1000; i++) {  
    NSImage *image = [[NSImage alloc] initWithContentsOfURL:inputUrl];  
    [image release];  
}

It performs quickly for the first several since there is plenty of free system memory, but eventually the system comes to its knees at what I'm calling the "thrashing point." Here, I believe the system relieves just enough memory to load the next image, so performance ends up being slow. Additionally, now other applications run slowly because the system has to free up this hogged, but now unused, memory.

What would make sense to me is if it would allocate the memory and then have the system free it so that my "Real Mem" statistic in Activity Monitor stays small rather than heading into the gigabytes with successive load/release iterations. In practice, I may never end up at this point, but it seems odd that my Activity Monitor "Real Mem" statistic eventually exceeds all other applications when the actual resident memory required at any time is generally small. I originally thought this was some sort of memory leak or caching issue, but it seems more related to aggressive memory allocation in the application and lazy memory freeing on the system (not that there's anything wrong if the OS has that policy--if that is in fact the way it works). Perhaps I'm missing something altogether though.

Is there a better way to repeatedly load images without this hogging-and-not-proactively-relieving memory usage behavior? Perhaps there is a way to force the application memory footprint down, or there is a way to be smarter about how images are loaded into the same objects or memory locations? My goal is to load an image, process it (get a thumbnail, change the image format, etc), get rid of it in memory, and then do it again--all without this observed memory growth.

--

FOLLOW-UP:

Bavarious, thanks. A wrapping NSAutoreleasePool does resolve the iterative memory growth when loading the same file:

NSURL *inputUrl = [NSURL URLWithString:@"file:///Users/me/Desktop/15MBjpeg.jpg"];  
for(int i=0; i<1000; i++) {  
    NSAutoreleasePool *apool = [[NSAutoreleasePool alloc] init];
    NSImage *image = [[NSImage alloc] initWithContentsOfURL:inputUrl];  
    [image release];
    [apool drain];
}

However, it does not solve the issue where the memory stays increased after the image is released (and the NSAutoreleasePool drained). For example, when loading my 15MB JPEG image, the "Real Mem" memory jumps from the 8MB steady state to about 25MB and then stays there. (My application only has an Interface Builder button that has a wired IBAction to a method that invokes only the for loop I copied). I would expect that after the for loop finishes (or if only one image is loaded and released) that the "Real Mem" statistic would decrease back to the nominal application level memory usage.

It seems reasonable that other things in the background can be loaded as well when invoking NSImage functionality, which can increase the memory. However, different sized images (15MB, 30MB, 50MB, etc) increase memory proportionally in the application, which leads me to believe it's more than such allocation.

Further, if I try to load separate images in succession (say, 15MBjpeg-1.jpg, 15MBjpeg-2.jpg, etc.) the memory sometimes compounds for each new image loaded. For example, if two images are loaded in succession, the "Real Mem" memory usage for the application after the loading/releasing is now roughly 50MB, and it never decreases--based on my observation. This behavior continues when loading subsequent images so that the application can creep into hundreds of MB of "Real Mem" memory usage after loading several large images.

Interestingly, if I reload the same image over and over again, the steady state memory does not increase. Does this indicate some sort of caching that's going on? Again, my goal is to batch process through several different image files without this growth in memory. Thanks in advance.

Oh, and I'm looking into the Heapshot Analysis article ATM, but wanted at least to post my progress and see if there's other input.

--

FOLLOW-UP #2

bbum, thanks for the great article. I ran Instruments Allocations with my test program and did not find any Heap Growth. As on the referenced blog post, my approach was to, 1) click my "Load and Release Image" button on my Interface Builder interface (which invokes the load/release behavior), 2) click "Mark Heap" several times every few seconds in Instruments Allocations, and then 3) repeat 1) and 2).

Using this approach, the Heapshots consistently reported 0 Bytes in the Heap Growth column over time (3 clicks every 5 sec for 15 seconds), meaning that there is no additional memory allocated from the baseline Heapshot. Additionally, in the Statistics pane, there is a Malloc of 13.25MB each time I click the "Load and Release Image" button, but Live Bytes is 0 Bytes, meaning that it has been fully released. If I click the "Load and Release Image" button three times, the Overall Bytes for the image reports 39.75MB (3 * 13.25MB), meaning that 39.75MB was allocated, but fully released since the Live Bytes is 0. The Allocations graph spikes up quickly and comes right back down, since it's a pretty quick operation. It all seems to make sense that there is no leak and no growth in the steady state use of memory.

But, now what do I do that my "Real Mem" statistic is still high? I know that Activity Monitor is not the standard for debugging memory issues. But, "Real Mem" still stays high until I close the program, and then the "Real Mem" all goes back into the "Free" category, which is bizarre to me.

I tested this same approach with two images (15MBjpeg-1.jpg, 15MBjpeg-2.jpg) by just duplicating the code in the same method, and I observe no Heap Growth again. Obviously more allocations were been made and released. However, now the "Real Mem" sits at roughly twice the increase as in the case of loading and releasing only one image. And again, it doesn't decrease in the test program's steady state.

Is there anything further I can do? Here is the test code for a single image load/release for anyone who wants to give it a try (just wire an IB button to openFiles):

#import "TestMemory.h" // only declares -(IBAction) openFiles:(id)sender;
@implementation TestMemory
-(IBAction) openFiles:(id)sender {
    NSURL *inputUrl = [NSURL URLWithString:@"file:///Users/me/Desktop/15MBjpeg.jpg"];
    NSAutoreleasePool *apool = [[NSAutoreleasePool alloc] init];
    NSImage *image = [[NSImage alloc] initWithContentsOfURL:inputUrl];
    [image release];
    [apool drain];
}
@end

Thanks for reading. :)

--

FOLLOW-UP #3

bbum, no I don't have garbage collection enabled (GC is set to Unsupported in the project settings). I investigated the memory using vmmap and the VM Tracker bar in Instruments Allocations. I assumed you meant the VM Tracker data when you said VM Instruments, since it reports the same information as vmmap. After opening a single image using my "Load and Release Image" button, some significant memory numbers include the following (from VM Tracker):

Type         %ofRes  ResSize  VirtSize Res%  %AllDirty  DirtySize
__TEXT       38%     33.84MB  80.45MB  42%   0%         0Bytes
*Dirty*     32%     28.23MB  114.99MB 24%   100%       17.11MB
MALLOC_LARGE 14%     13.25MB  13.25MB  100%  0%         4KB
Carbon       11%     9.86MB   9.86MB   100%  20%        3.46MB
VM_ALLOCATE  9%      8.43MB   48.17MB  18%   49%        8.43MB
...

Interestingly, subsequent load/releases of a single image increase the "Resident Size" of the Dirty and VM_ALLOCATE types by ~.3MB, and the "Dirty Size" of these types also increases over time. (VM_ALLOCATE seems to be a subset of Dirty). No other types appear to change with subsequent load/releases.

I am unsure exactly what to take away from this data, or how I can use it to make the program release the memory. It seems like the VM_ALLOCATE type may be the chunk that is not being freed, but that's only speculation. Is it possible that the some portion of the underlying NSImage init implementation saves a cache of the image file and won't release it? Again, as stated earlier, it intrigues me that each subsequent load/release of the same image file hardly consumes resources (CPU, disk grinding, etc) and wall clock time compared to the first load/release. Thanks in advance.

解决方案

  • What Bavarious said; have you tried surrounding it with an NSAutoreleasePool.

  • That is a classic micro-benchmark. While it certainly indicates a problem, the problem may actually be that the benchmark is divergent from expected real world patterns so much that the bug lies with the benchmark.

  • In an efficiently designed application, it would not read the same image data from disk more than once.

  • This is a prime candidate for Heapshot analysis.


(Thanks for the follow-ups; helpful!)

The symptoms you describe sound like either a VM leak (something is consuming addresses without doing allocations; mapped memory, for example) or a cache that isn't being pruned (that contains VM allocations).

  • do you have GC enabled? If so, this could easily be because the GC threshold isn't triggered. The collector doesn't know to credit Really Large Allocations from non-GC to the GC zone. If you force collections, it'll work around this particular edge case.

  • Try having a look at the VM instrument or use vm_map at the command line to have a look at what is consuming the address space within your app.

这篇关于在加载和释放NSImage时,内存持续增加的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆