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

AnimationView init blocks main thread for a noticeable amount of time #872

Closed
5 tasks done
tmg-mlyons opened this issue Apr 17, 2019 · 22 comments
Closed
5 tasks done

Comments

@tmg-mlyons
Copy link

Check these before submitting:

  • The issue doesn't involve an Unsupported Feature
  • [] This issue isn't related to another open issue

This issue is a:

  • Non-Crashing Bug (Visual or otherwise)
  • [] Crashing Bug
  • [] Feature Request
  • Regression (Something that once worked, but doesn't work anymore)

Which Version of Lottie are you using?

Lottie 3.0

What Platform are you on?

  • [] MacOS
  • iOS

What Language are you in?

  • Swift
  • [] Objective-C

Expected Behavior

Initializing an AnimationView should not block the main thread for a significant amount of time.
The previous version LOTAnimationView did not block the main thread on init.

Actual Behavior

Initializing an AnimationView blocks the main thread long enough where it appears that the app has hanged.

Code Example

convenience init(fileUrl: URL, imageProvider: AnimationImageProvider? = nil, animationCache: AnimationCacheProvider? = LRUAnimationCache.sharedCache) {
    let animation = Animation.fileUrl(fileUrl, animationCache: animationCache)
    let provider = imageProvider ?? FilepathImageProvider(filepath: fileUrl.deletingLastPathComponent().path)
    self.init(animation: animation, imageProvider: provider)
}
let animationView = AnimationView(fileUrl: url)

Animation JSON

treasurechest-animation1.json.zip

@JoeSzymanski
Copy link

@buba447 This is a separate issue related to the same large animation file being discussed in #871. We are working on making the file smaller, but are still worried about the main thread hangs on creating AnimationView objects.

@JoeSzymanski
Copy link

Starting to look into this a little bit. First point of note, the decoding of the Animation object (using our troublesome animation file) from the JSON currently takes around 350ms. I'm looking to see if there's a way to improve the parsing at all, but for complex models, it's possible that manual code will be significantly more performant than Decodable.

@buba447
Copy link
Collaborator

buba447 commented Apr 17, 2019

Dang, yeah it seems that Codable doesnt perform very well with complex object mappings:
https://medium.com/@zippicoder/performance-of-decoding-automatically-in-swift4-f089831f05a5

@buba447
Copy link
Collaborator

buba447 commented Apr 17, 2019

The objects are being parsed on the main thread, we could offload those onto another thread and then feed them back into the animation view.

In the meantime you can initialize an animation view without the animation, and in another thread initialize the animation. You could also initialize the animation object early, and let the animation cache provide it to the animation view on allocation. This is a bit of a work around but would help a lot with large animations.

@JoeSzymanski
Copy link

I'm going to see if I can spend a little more time digging into this. I might see how hard it would be to support Dictionary-based initialization in parallel to Decodable support to do performance testing.

I also need to go back and see if there are other performance bottlenecks, as the decoding problem only appears to be half the issue. The initialization of the node tree is another bottleneck on the main thread.

@buba447
Copy link
Collaborator

buba447 commented Apr 17, 2019

And Just a heads up @JoeSzymanski #873 also made changes to how the node tree is initialized. Hidden layers and nodes are skipped during tree initializaiton, which should help with performance hits. Im sure theres tons of performance fixes to be had though.

Thanks!

@JoeSzymanski
Copy link

Thanks for the heads up. I'll do some more performance testing with the latest master.

@JoeSzymanski
Copy link

I put together a proof of concept for decoding an Animation class from the JSON rather than using Codable. See https://github.com/JoeSzymanski/lottie-ios/tree/initFromJSON

In testing, it drops the decoding time from ~350ms to ~70ms. This includes the fact that I haven't finished the implementation for KeyFrame related classes (very complex classes to decode, and this PoC works without requiring that).

I can see about cleaning the implementation up if you think it makes sense to move forward in that direction, but it might take me some time to get around to a full clean implementation.

@buba447
Copy link
Collaborator

buba447 commented Apr 18, 2019

It's a shame that decodable is not very performant. My hope was to have built in support for encoding and decoding.

I see that you aren't decoding shapes or anything downstream from that? Shape objects usually take up more than 80% of the JSON so I wonder what the performance would be like if shapes were also decoded. I don't think we can get an idea what the true performance difference without decoding shapes or doing some sort of testing there.

If there is a huge performance difference then it makes sense to move forward here. I wonder if theres any quick wins or tricks to squeeze performance out of codable?

@JoeSzymanski
Copy link

I'll have to look more into that. I'm not 100% sure how much is happening in the decoding logic for Animation versus in other locations. I know that my current code doesn't decode LayerModel.transform or LayerModel.masks via the JSON (it reverts back to the Decodable handling of those).

I was trying to resolve only the one side of the problem, which is the initial decoding. There are additional performance gains that can be made in setting up the animation within AnimationContainer, too, I think.

@JoeSzymanski
Copy link

@buba447 I'm looking more into this now and realized that my code misses a very important step in the decoding process around the layer model and was not decoding all of the subtypes for that array. It looks like that is the core performance hit that I can find, so I'm looking a little more today into that to see if there's a specific place where we're hitting the performance problem.

@JoeSzymanski
Copy link

As I do a little more digging, I'm leaning towards the handling of the heterogeneous collections as one of the big performance bottlenecks: https://github.com/airbnb/lottie-ios/blob/master/lottie-swift/src/Private/Model/Extensions/KeyedDecodingContainerExtensions.swift#L27-L40

In doing some logging of decoding times, a simple ShapeLayerModel that only has one item in it (A Group item with 3 sub-items) can take up to a full millisecond to decode. Cases with more sub-items can be longer. When there are a large number of items, the decoding time seems to increase significantly.

I'm not sure if there's a better way to handle it, and I don't know if I have time at the moment to dig into a full JSON-based replacement to get performance numbers out of this.

@JoeSzymanski
Copy link

As an update, I was able to dig into this again, and created a solution that cut the decoding time for our complex animation from ~285 ms to ~90-100 ms.

Now for the negatives. To do this, I had to create a completely new and parallel decoding system, which is rife with potential decoding errors compared to the existing code paths. In addition, most of the time saving appears to be coming from making the decoding of arrays of class families concurrent, which isn't possible with the current Decoder system.

I'm going to do a little more digging to see if I can find a way to make the existing code path work a little faster, maybe by trying to find a way to do the array decoding in parallel, if possible.

@JoeSzymanski
Copy link

@buba447 I've done ab it more testing, and I think this code is hitting the limits of what Decodable can do with current support levels. Unfortunately, Decodable doesn't have any kind of concurrent support (and didn't seem to get much traction for adding that in the one Swift thread I found), and converting the data to a Dictionary to do the concurrent processing make it significantly slower.

The only option I see here looks like it's removing the Decodable support and moving to a JSON dictionary-based initialization, but I know that this is complex and error prone, as I saw trying to implement it myself. It does provide significant performance gains, but will be hard to get finished 100%.

Thoughts?

@buba447
Copy link
Collaborator

buba447 commented Jun 4, 2019 via email

@JoeSzymanski
Copy link

I pushed my current code into the branch at https://github.com/JoeSzymanski/lottie-ios/tree/initFromJSON (with some attempts to improve the existing decodable logic). Let me know if you want me to dig more into that.

@NeverwinterMoon
Copy link

NeverwinterMoon commented Jun 16, 2020

The objects are being parsed on the main thread, we could offload those onto another thread and then feed them back into the animation view.

In the meantime you can initialize an animation view without the animation, and in another thread initialize the animation. You could also initialize the animation object early, and let the animation cache provide it to the animation view on allocation. This is a bit of a work around but would help a lot with large animations.

My original problem was that the new page couldn't be presented immediately while the Lottie JSON file was being loaded on the new page, so there was a hiccup on page transition. I've tried loading the JSON file in a different thread and it did help with the transition, but had its problems, because I was still using the main thread. I've tried using a background thread, but Lottie crashed, saying it required the main thread. So, now I can make it so the new page is presented before Lottie loads the JSON file but the user can't leave the page before that loading is finished (and the whole page is unresponsive during the loading).

Just to be sure I don't misunderstand something: does Lottie indeed require the main thread when loading/parsing the animation file?

Some additional info. I am loading the file from the bundle and this call blocks the main thread: animationView.animation = Animation.named("someAnimation"). The file size is pretty big - around 6 MB. Another thing you suggested was to load the file early, but if it still has to be done on the main thread, it would still block the whole UI no matter when I load it...

@gowtham1094
Copy link

gowtham1094 commented Sep 19, 2020

Even i faced the same issue, view gets freeze on time of loading the animation since trying to load big json.
I tried to initialise the animation in background thread and then assign to animation view in main thread which removes the freeze.

self.showProgressView() DispatchQueue.global(qos: .userInitiated).async { let animation = Animation.named(self.type.animation) DispatchQueue.main.async { self.hideProgressView() self.animationView.animation = animation self.play() } self.animation = animation }

@cminero10
Copy link

Even i faced the same issue, view gets freeze on time of loading the animation since trying to load big json.
I tried to initialise the animation in background thread and then assign to animation view in main thread which removes the freeze.

self.showProgressView() DispatchQueue.global(qos: .userInitiated).async { let animation = Animation.named(self.type.animation) DispatchQueue.main.async { self.hideProgressView() self.animationView.animation = animation self.play() } self.animation = animation }

It helped me! thanks!

@jatinRB
Copy link

jatinRB commented Jan 7, 2021

well i face an issue while recording animation, whenever i try to record animationview it will animate with lang and after complete record it seem work perfectly. have you guys ever faced issue like this than let me know please.

and i am sure it's issue of main thread so anyone have idea, how to solve it please share me.

@manhmeu1211
Copy link

@buba447 Any updates?

@calda
Copy link
Member

calda commented Jul 6, 2022

3.4.0 includes a new parsing implementation that is 2x faster than the previous Codable implementation (#1561). For large Lottie animations, I'd also recommend parsing the Animation on a background thread rather than on the main thread (parsing is thread-safe).

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

9 participants