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

Improve how Gifu uses system resources #191

Open
kean opened this issue Jul 26, 2023 · 2 comments
Open

Improve how Gifu uses system resources #191

kean opened this issue Jul 26, 2023 · 2 comments
Assignees

Comments

@kean
Copy link
Collaborator

kean commented Jul 26, 2023

The current state

GIF playback involves bitmapping individual frames, scaling them down to decrease memory usage (optional), and displaying them by swapping the bitmaps on screen. There needs to be a bit of a buffer to support this playback without stuttering. Bitmapping is CPU intensive, so there are advantages of caching bitmaps in memory to save CPU time. It's classic CPU vs memory trade-off.

Gifu has a dedicated animator with a buffer (FrameStore) that holds up to 50 frames in memory. If you visualize the playback, it looks something like this:

111111111111111111111111111111111111111111111111111000000000000000000000000000000000000
000000000000011111111111111111111111111111111111111111111111111100000000000000000000000
000000000000000000000000000011111111111111111111111111111111111111111111111111100000000

Issues

There are a couple of issues in which the system doesn't do the best possible job in terms of managing the system resources:

  1. If a GIF has over 50 frames, Gifu keeps re-bitmapping the same frames, even if the free RAM is abundant.
  2. Preparing the GIF for rendering by pre-loading the first 50 frames delays its playback.
  3. If you display the same GIF in multiple views, the cached frames and the work to generate them are not shared between the animators.
  4. There is no ceiling on how many GIFs can be played at the same time. If you end up playing too many, you may oversaturate both the CPU and the RAM, causing all of them to stutter, or may even get the app terminated.
  5. When the app is suspended, the bitmapped frames stay in memory, increasing the chance the Watchdog terminates the app.
  6. The framework doesn't respond to memory warnings.

Suggested Solution

Specifying the buffer size in terms of the number of frames is not optimal. There may be GIFs with few high-resolution frames, so they use too much memory. Or there may be GIFs with many tiny frames, but Gifu will not be able to hold all the frames in memory.

I suggest thinking about the upper limit in terms of the number of MB of RAM or the percentage of RAM. The framework could leverage NSCache to cache the frames. So, to summarise, I propose the following changes:

  • Identify the minimum buffer size required for smooth playback. It should be a maximum of two or three frames on modern CPUs.
  • Determine a fallback strategy if the production of frames is slower than consumption
  • Add a shared cache (NSCache) to store the bitmapped frames in memory
  • The cache should manage a single pool of memory, but individual images should have dedicated slots in it, so that when the animator is deallocated, it could also remove all of the associated data from the cache.
  • Add a shared system for producing the frames that could coalesce the requests for the same frames and manage how many images can be played in parallel
  • Clear cache for a given image when its playback stops
  • Clear cache when the app goes into the background
  • Also, clear the playback buffer and only leave the single (current) frame in memory
  • Provide an API for injecting a custom cache instance in case the app that uses the framework wants to leverage its own cache instance and control its size and cleanup policy
  • Automatically determine the number of GIFs that can be animated in parallel based on their size (this should be opt-in)

These suggestions are largely theoretical, but they are based on the following assumptions that I tested and that were also explained in the following WWDC session:

  • On iOS, it's nearly impossible to get the app terminated by allocating a ton of memory as long as the app responds to memory warnings and clears it. However, to be a good citizen, the app should not use too much memory, or it may get other apps killed.
  • If the app is suspended and it's using a lot of memory, it significantly increases the risk of it getting terminated.
@kean
Copy link
Collaborator Author

kean commented Jul 27, 2023

@kaishin, this is a follow up to over conversation. I'd love to get your feedback before I proceed with the implementation.

@kaishin
Copy link
Owner

kaishin commented Dec 18, 2023

@kean Recently I started reviewing the entire codebase, including this aspect, but my involvement with other projects has significantly slowed that down. I am hoping to resume that, including going over this issue, in the coming weeks.

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

No branches or pull requests

2 participants