Loading Many UIimages from Disk Blocks Main Thread

Loading many UIImages from disk blocks main thread

I would suggest you try UIImage(contentsOfFile:) instead of UIImage(named:). In my quick test and found it to be more than an order of magnitude faster. It's somewhat understandable because it's doing a lot more (searching for the asset, cacheing the asset, etc.).

// slow

@IBAction func didTapNamed(_ sender: Any) {
let start = CFAbsoluteTimeGetCurrent()
imageView.animationImages = (0 ..< 20).map {
UIImage(named: filename(for: $0))!
}
imageView.animationDuration = 1.0
imageView.animationRepeatCount = 1
imageView.startAnimating()

print(CFAbsoluteTimeGetCurrent() - start)
}

// faster

@IBAction func didTapBundle(_ sender: Any) {
let start = CFAbsoluteTimeGetCurrent()
let url = Bundle.main.resourceURL!
imageView.animationImages = (0 ..< 20).map {
UIImage(contentsOfFile: url.appendingPathComponent(filename(for: $0)).path)!
}
imageView.animationDuration = 1.0
imageView.animationRepeatCount = 1
imageView.startAnimating()

print(CFAbsoluteTimeGetCurrent() - start)
}

Note, this presumes that you had the files in the resource directory, and you may have to modify this accordingly depending upon where they are in your project. Also note that I avoided doing Bundle.main.url(forResource:withExtension:) within the loop, because even that had an observable impact on performance.

Loading many buffered images 100+ to memory in java

It sounds unreasonable that loading an image from disk takes a couple of seconds, unless the images are really, really BIG (10 Megapixels or more). First determine where the time is really spent.

For caching, you could hold references to each image you loaded using java.lang.ref.SoftReference - this allows the image to be garbage collected before you run OOM.
If really unavoidable, I would use a multilayered caching system, first level with SoftReferences in memory, and for server based images an additional on-disk cache. When looking for an image, the in-memory cache is checked first, if nothing is found there the disk cache is checked and if still not found the image is loaded the usual way.

Android fast Bitmap loading

One option would be to use create image cache using the WeakReference so that the image would be removed from the memory when system encounter low memory situation. This way you can keep images in memory and only need load from sdcard when they are not in memory. So your current activity would always keep the hard reference to the bitmap's required and the image cache would keep the weak reference to the bitmap's.

Following is some more information about weak reference:

JavaDoc weakReference

StackOverflow post discussing using weak reference for cache

Loading many images and running out of memory when using NativeJpg

I don't know for sure if this is your problem, but there is a huge design flaw with your current code. You are creating 1 thread per image. Assuming that you have hundreds or thousands of threads this design cannot scale.

For a start there is a significant overhead associated with creating, starting and terminating threads. You don't want to pay that overhead time and time again.

But more problematic is the resource overhead for a thread. Each thread has its own private stack space. The address space (1MB) for that stack is reserved (but not committed) when the thread is created. With enough threads you will exhaust your address space, even though your actual memory commit level is still low.

I strongly urge you to abandon that code and start again. You should use one of the established threading libraries. Threading is hard to do well and you need a lot of knowledge and expertise to do it well. Use either OmniThreadLibrary or AsyncCall.

What you are looking for is a simple thread pool with a small number of threads. You should then simply feed tasks (i.e. image file names) to the thread pool and let it manage the processing of those tasks.

Main thread blocking on scroll and Kingfisher setting the image

When the images are larger than the image view, iOS needs to manipulate large UIImage objects, which can cause observable stuttering in the UI. You can prevent that problem by resizing the images before using them.

Fortunately, Kingfisher has a processor that can resize these (in a background thread) for you. As the Cheat Sheet says:

Using DownsamplingImageProcessor for high resolution images

Think about the case we want to show some large images in a table view or a collection view. In the ideal world, we expect to get smaller thumbnails for them, to reduce downloading time and memory use. But in the real world, maybe your server doesn't prepare such a thumbnail version for you. The newly added DownsamplingImageProcessor rescues [sic]. It downsamples the high-resolution images to a certain size before loading to memory:

imageView.kf.setImage(
with: resource,
placeholder: placeholderImage,
options: [
.processor(DownsamplingImageProcessor(size: imageView.size)),
.scaleFactor(UIScreen.main.scale),
.cacheOriginalImage
])

Typically, DownsamplingImageProcessor is used with .scaleFactor and .cacheOriginalImage. It provides a reasonable image pixel scale for your UI, and prevent future downloading by caching the original high-resolution image.

I created a little test with small images and confirmed that it was silky smooth, but when I used large images, I experienced stuttering in the scrolling behavior. But when I added this DownsamplingImageProcessor in the large image scenario, it was silky smooth again.

Improved image storage/import performance on HDD

First of all, you use a thread pool with far more threads than the number of cores on the i9-12900KF processor. Having two threads running on the same physical core generally cause them to be slower. If they run on the same logical core, then they cannot run simultaneously (they will be constantly interrupted). In fact, even if they run on different physical cores, one thread can significantly slow down another if it intensively make use of the L3 cache or the memory which is likely your case. Operating on a large buffer can causes cache lines of the cache of other cores to be evicted and thus reloaded later. This is known as cache trashing. This problem can become critical with non-contiguous loads/stores.

The target processor is a big-little one so the scheduling of threads on such a processor is more complex than usual. In fact, many libraries do not support well such architecture yet (they are not running efficiently). Even OS stacks are barely suited for such kind of architecture (at least on Windows and Linux). The number of threads per core is not the same for all core: big core can execute 2 threads simultaneously (sharing available resources) while little core can only execute 1 thread at a time. It is worth noting that the frequency of the little core is not the same than the big core: 2.4 GHz VS 3.2 GHz for the base frequency and 3.9 GHz vs 5.1 for the turbo frequency). Regarding the scheduling of the thread to the core, the performance of the target thread can change.

The frequency of the cores running the threads is dependent of the number of cores used and the work done on each cores. For example, running a computationally intensive code using the FP AVX-2 units (or the non-officially supported AVX-512 units) on a core can significantly reduce the frequency of other cores. The higher the number of active core, the lower the frequency. Dynamic frequency stalling affect the scalability of application but this scaling is important for the processor to fulfil its power budget (and not melt too).

Caching also matters a lot. Indeed, mainstream OS tends to put HDD read/written data in memory so to operate faster. This requires some additional memory which is not considered allocated. When a process request a large amount of memory, the OS flush/invalidate the IO cache regarding the requested space and later accesses cause data to be reloaded from the storage device (much slower). The solution is to check the amount fully available memory (the part not cached) and not to use too much memory if the remaining space is used by the storage device cache.

Having two thread doing IO operations is generally not faster than 1 thread on HDD (especially with 1 head). Some OS stacks use locks if not even a giant lock. Because of that, one loading thread with asynchronous IO can be faster than blocking IO on one/multiple threads. Indeed, the OS can reorder requests so they can be more contiguous in that case (so to reduce the seek time by loading data on the way).

is AVAssetImageGenerator completion handler called in main thread?

IIRC, yes. But you can test it, and to be safe you can wrap your code in a block which dispatches it to the main thread.

Generally speaking, the callback has to return to the main thread as the thread it's started from can't be guaranteed to be running a run loop if it isn't the main thread.

Unless you're scheduling the block you're creating in relation to other operations (dependencies) then I'm not sure what advantage the block gives you as the image loading is asynchronous so you can trigger it from the main thread without blocking anything.


From your comment, in that case you should switch your code around. Create the block operation inside the completion block which provides you with the image. Add each block operation to your queue. The block operation just takes the image and saves it to disk.

Is there an option to make ImageLoader.display(...) call block current (UI) thread until image is loaded?

There's now a method which can load and scale Bitmap synchronously: https://github.com/nostra13/Android-Universal-Image-Loader/blob/master/library/src/com/nostra13/universalimageloader/core/ImageLoader.java#L550

But it's almost never a good idea to do this, because GUI applications should be responsive, that means — never block UI thread for a noticeable time.

Glide cache image on disk on background thread

GlideApp.with(context)
.downloadOnly()
.diskCacheStrategy(DiskCacheStrategy.DATA) // Cache resource before it's decoded
.load(url)
.submit(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
.get() // Called on background thread


Related Topics



Leave a reply



Submit