kean / Nuke

Image loading system
https://kean.blog/nuke
MIT License
8.13k stars 527 forks source link

High memory usage (CG Raster Data) #118

Closed vpanghal closed 7 years ago

vpanghal commented 7 years ago

I am using Nuke to display image in a collection view. This collection has many images that are downloaded from object store and resized to cell size. In the profiler, I see memory usage (CGRaster Data) keep growing. It seems Nuke(5.0.1) is retaining the strong reference to this data.

screen shot 2017-02-14 at 11 18 37 pm

kean commented 7 years ago

Hey! Nuke has a built in memory cache that stores rasterized images (CG raster data. That seems about it.

The memory usage seems a bit too high for iPhone 5S though. Please let me know if you receive any memory warnings.

vpanghal commented 7 years ago

I am getting memory warning and app is terminating after this. I see Nuke is dropping its cache on receiving memory warning but CGRaster data usage is not dropping.

On side note - What is cached in Nuke default cache? Is it network fetched data/ some intermediate representation/resized image?

kean commented 7 years ago

I see Nuke is dropping its cache on receiving memory warning but CGRaster data usage is not dropping.

I would suggest to disable Nuke's memory cache and see if you still can reproduce the problem. You might have some image views that don't get deallocated. Every image view holds a strong reference to the raster that it's displaying. Make sure that all your view controllers and views get deallocated properly. You might want to use visual memory debugger.

On side note - What is cached in Nuke default cache?

Nuke.Cache.shared is a default memory cache which is used by the default Nuke.Manager. To disable it you can create your own shared manager without a memory cache:

let manager = Nuke.Manager(loader: Nuke.Loader.shared, cache: nil)

Is it network fetched data/ some intermediate representation/resized image?

No intermediate representations are stored, only processed images which get displayed in your image views.

P.S. I periodically perform generational analysis using the demo project. I create a new Nuke.Manager instance, push a screen with a collection view with some images, than pop the screen and check if everything get deallocated. I haven't seen any leaked Nuke objects yet, only some UIKit crap like UIFont caches, UIStatusBar icons, etc.

screen shot 2017-02-14 at 21 36 20

vpanghal commented 7 years ago

Hi Kean,

Thanks for these very useful pointers! As you suggested, I disabled Nuke memory caching but I still observe that CGRaster usage is growing and eventually iOS is terminating app.

I think issue is Nuke is decoding the image in parallel and each thread race to allocates (30MB) separate mapped region to process the image . And from traces, it looks like there are seven threads. So that would initially allocate 210MB.

e.g. Here is the allocation happen on loading image in Collection view. There are seven memory mapped region of 30MB each allocated within 3 second time window

cg raster timestamp

kean commented 7 years ago

I just realized that the images that your are displaying are probably enormous in size. I tested Nuke demo with 2000x3000px images and they result in only 22.89 MB rasters.

To fix the problem you should download images which are smaller in size and/or resize loaded image to fit your image views. If you still need to display full-size images, do it fullscreen, not in a collection view.

The best way to resize images is to replace default Decompressor with the one with a target size. This way only the resized rasters are created, never the full-size ones:

func makeRequest(url: URL, view: UIImageView) -> Request {
    var request = Request(url: url)
    request.processor = AnyProcessor(Decompressor(targetSize: targetSize(for: view), contentMode: .aspectFill))
    return request
}

func targetSize(for view: UIView) -> CGSize { // in pixels
    let scale = UIScreen.main.scale
    let size = view.bounds.size
    return CGSize(width: size.width * scale, height: size.height * scale)
}

I've tested this code in a demo again (2000x3000 images, 4 images per row, no mem cache) and now the memory usage never grows more than 40 MB:

screen shot 2017-02-15 at 10 30 43

P.S. The resizing API isn't very elegant at the moment. I'll add some convenience methods soon, and I'll also update docs based on your feedback. Cheers 🍻

I think issue is Nuke is decoding the image in parallel

decoding+processing are performed on a serial queue specifically to avoid using too much resources

vpanghal commented 7 years ago

Perfect! Decompressing/resizing images to target view size did help and I am not observing unbounded memory growth with in memory cache enabled in the Nuke. App has around 2000s of images across multiple collection view. App is not getting memory warning and usage is capping around 200MB.

Right now these image are scaled down to low resolution for full view. But As you suggested, I would need to have another thumbnail of the image for collection view to reduce cpu usage in decompression/resizing. Hopefully that would make user experience little better. Thanks for all your help! Let me know if I can help anyway. Cheers :beers: