Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Animated webp sd_memoryCost 40 times higher than content-length on browser #3668

Closed
simonmcl opened this issue Jan 24, 2024 · 9 comments
Closed

Comments

@simonmcl
Copy link

Issue Info

Info Value
Platform Name ios
Platform Version 17.1
SDWebImage Version 5.18.10
Integration Method SPM
Xcode Version Xcode 15.1
Repro rate 100%

Issue Description and Steps

I'm integrating a server that returns certain webp images in different sizes. Some animated, some not. Using small animated images where content length is 1.3 mb, SDWebImageCache loads them and animates fine, with the image?.sd_memoryCost being quite close to the content-length / download size

e.g. https://data.mantodev.com/media/mobile180/ipfs/QmZhHfAQsw2kugnbCeAXwdFHuQmajWhxtWkFzVNoaexSkD

However when using a large size, where the content-length is 12.3mb, the image?.sd_memoryCost skyrockets to 481 MB. This blocks the main thread for a long time, crashing the app.

e.g. https://data.mantodev.com/media/mobile900/ipfs/QmZhHfAQsw2kugnbCeAXwdFHuQmajWhxtWkFzVNoaexSkD

For now, i've added some code to check for max memoryCost and if it exceeds, just load the first frame and don't animate. But this offers a poor UX, especially since the small one animates fine in the "thumbnail view", and is then frozen in the "detail view".

Can someone have a look at why the large image has such a large different in sizes?

And is there any other suggested ways I could handle this? I tried adding some transformers to decrease the size, but it still caused the main thread to block

@dreampiggy
Copy link
Contributor

You seems don't understand what's RAM occupation of image

The data length != bitmap size

The bitmap size = bytes per pixel * pixel count * frame count = bytes per pixel * width * height * frame count

For example, a 1000x1000 GIF with 100 frames will occpuy 381MB

See: https://docs.huihoo.com/apple/wwdc/2018/219_image_and_graphics_best_practices.pdf

@dreampiggy
Copy link
Contributor

You should avoid using UIImageView to display huge animated image. That's not design for this case

Use SDAnimatedImageView, which is like a video player to only decode next frame and free the old frame. Just like browser.

See: https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#animated-image-50

@simonmcl
Copy link
Author

@dreampiggy I am using SDAnimatedImageView

I've done some more testing and it looks like its connected with setting config.maxMemoryCost on a custom cache. Even if I set it to 10x what the image needs, my main thread gets blocked and the Xcode app performance shows the RAM constantly increasing, going from ~98 mb to over 350 mb before it crashes

If I comment out that line, and leave the cache memory unbounded, it loads fine and the app RAM never exceeds 102 mb

any ideas what this could be?

@dreampiggy
Copy link
Contributor

You means, calling sd_memoryCost on SDAnimatedImage has bad effect and block caller thread ?

@dreampiggy
Copy link
Contributor

my main thread gets blocked and the Xcode app performance shows the RAM constantly increasing

Can you provide the Instrumentation report (archive zip or send via email to me) ?
Or a minimum reproduce demo ?

I check the sd_memoryCost implementation, it should not trigger extra image decoding logic and should not block the caller thread.

@dreampiggy
Copy link
Contributor

dreampiggy commented Jan 26, 2024

However when using a large size, where the content-length is 12.3mb, the image?.sd_memoryCost skyrockets to 481 MB.

What's this image instance be ? I guess this should be a _UIAnimatedImage, not SDAnimatedImage

How did you use the decoding and downloader ? When a request from SDAniamtedImageView, it pass a custom animated image class context and use that instead of UIImage for decoding and can cause lower memory usage. I guess this is the issue happends

@simonmcl
Copy link
Author

@dreampiggy

You means, calling sd_memoryCost on SDAnimatedImage has bad effect and block caller thread ?

No, calling imaveView.sd_setImage(...... I was using sd_memoryCost to test what was going on.

I've done some more digging and i've found the issue. Using the prefetcher, results in sd_setImage using _UIAnimatedImage instead of SDAnimatedImage. If I comment out the prefetch, everything works fine and the sd_memoryCost is ~1mb. With the prefetch its ~481mb

Code:

@IBOutlet weak var imageView: SDAnimatedImageView!

private let cache = SDImageCache(namespace: "custom-cache")
private var prefetcher: SDWebImagePrefetcher? = nil

private let otherImages: [URL] = [
	URL(string: "https://data.mantodev.com/media/mobile900/ipfs/QmZhHfAQsw2kugnbCeAXwdFHuQmajWhxtWkFzVNoaexSkD")!
]

override func viewDidLoad() {
	super.viewDidLoad()
	
	cache.clearDisk()
	cache.clearMemory()
	
	cache.config.maxDiskAge = 3600 * 24 * 7 // 1 Week
	cache.config.maxMemoryCost = UInt(500 * 1000 * 1000) // 500 MB
	
	prefetcher = SDWebImagePrefetcher(imageManager: SDWebImageManager(cache: cache, loader: SDImageLoadersManager()))
	
}

override func viewWillAppear(_ animated: Bool) {
	super.viewWillAppear(animated)
	
	prefetcher?.prefetchURLs(otherImages, progress: { _, _ in
		print("prefetch progressed!")
		
	}, completed: { [weak self] processed, skipped in
		guard let self = self else {
			print("Error grabbing self")
			return
		}
		
		let bigUrl = URL(string: "https://data.mantodev.com/media/mobile900/ipfs/QmZhHfAQsw2kugnbCeAXwdFHuQmajWhxtWkFzVNoaexSkD")
		
		var context: [SDWebImageContextOption: Any] = [:]
		context[.imageCache] = self.cache
		
		self.imageView.sd_imageIndicator = SDWebImageActivityIndicator.gray
		self.imageView.sd_setImage(with: bigUrl, placeholderImage: nil, context: context, progress: nil) { image, error, cacheType, returnedUrl in
			print("Big image size: \(image?.sd_memoryCost ?? 0)")
		}
	})
}

Run this and it will print the below and hog the main thread

Big image size: 481000000

Comment out the URL inside otherImages at the top (leaving the array empty), run again, it will not use the prefetch and it will print the below and render the image fine

Big image size: 1008000

Why does this prefetcher cause it to use _UIAnimtaedImage, and how can I disable / stop that?

@dreampiggy
Copy link
Contributor

Just use prefetch with context[SDWebImageContextAnimatedImageClass] = SDAnimatedImage.class

This is common mistake since you load images without a imageView as context. We don't know your preserved one.

I fired a proposal to make this more easy to customize, see that #3669 ans comment there

@simonmcl
Copy link
Author

@dreampiggy that worked perfect, thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants