Skip to content
This repository has been archived by the owner on Feb 2, 2023. It is now read-only.

[Performance] Looking for input on tuning performance with AsyncDisplayKit #1446

Closed
fatuhoku opened this issue Mar 28, 2016 · 11 comments
Closed

Comments

@fatuhoku
Copy link

I used RRFPSBar to measure the performance of my ASCollectionView and found that while the iPad Air manages to maintain a steady 60FPS, the iPad 2 struggles much more. There are down-spikes going down to around 7 FPS at times.

The iPad 2 is a multi-core device and I expected that ASDK would bring performance benefits.

I was wondering what have been your experiences when working with slower devices like the iPad 2?

@appleguy
Copy link
Contributor

hehe. I can promise you this is not ASDK's fault.

Why? The iPad 3 has fully 4 times as many pixels to draw, because of Retina - and it has only 1.3x the CPU power, at most. We get almost uninterrupted 60FPS on it with the extremely complex Pinterest feed layout, even with fast scrolling.

I'd be thrilled to help you optimize your app, because huge improvements should only take an hour or two of work. We have plans for documentation that will help demonstrate best practices that ensure performance is good on the first try with ASDK, as well as simple steps you can use to quickly dial in performance for your app-specific needs depending on a few details of the UI (e.g. what areas animate, are tappable, etc allows you to flip on very powerful optimizations - isLayerBacking and shouldRasterizeSubnodes are tremendously impactful for performance and you should use them everywhere your app design allows, with only a boolean = YES change in the needed areas)

@appleguy appleguy changed the title ASDK performs not amazingly on iPad 2 (collection view can drop frames to as low as 7 FPS down-spikes) [Performance] Looking for input on tuning performance with AsyncDisplayKit Mar 30, 2016
@appleguy appleguy added this to the 2.0 milestone Mar 30, 2016
@appleguy
Copy link
Contributor

cc @levi @maicki @nguyenhuy @hannahmbanana @lappp9 @RCacheaux about the very good point that we need to ensure the documentation we're all working on includes all the details necessary for most app developers to hit 60FPS at least on the iPad 2 and iPhone 4S (iPad 3 or iPhone 4 is a far harder bar)

@fatuhoku
Copy link
Author

Hi @appleguy — if you don't mind putting my use case here directly, I'm using ASDK to help provide a snappier UX recipe cards that look like this:

screen shot 2016-03-31 at 14 03 42

The contents of the cell is held in a MESMinicardContentNode with the following properties (I won't show init)

@property(nonatomic, strong) ASNetworkImageNode *avatarImageNode;
@property(nonatomic, strong) ASTextNode *authorNameNode;
@property(nonatomic, strong) ASNetworkImageNode *recipeImageNode;
@property(nonatomic, strong) ASTextNode *recipeTitleNode;
@property(nonatomic, strong) MESGradientNode *gradientNode;
@property(nonatomic, strong) ASImageNode *totalDurationIconNode;
@property(nonatomic, strong) ASTextNode *totalDurationTextNode;
@property(nonatomic, strong) ASImageNode *favouritesIconNode;
@property(nonatomic, strong) ASTextNode *favouritesTextNode;

... all in a flat hierarchy. Two network image nodes per cell. They both should fade their images in. Layer-backing / rasterisation seems to interfere with fading in of the placeholders (understandably)! :(

The cell itself is called MESMinicardCellNode, which wraps a MESMinicardContentNode thusly:

#import "MESMinicardCellNode.h"

#import "MESMinicardContentNode.h"
#import "MESMiniCardViewModel.h"

@interface MESMinicardCellNode ()
@property(nonatomic, strong) ASDisplayNode *contentNode;
@end

@implementation MESMinicardCellNode

- (instancetype)initWithViewModel:(MESMiniCardViewModel *)viewModel {
    self = [super init];
    if (self) {
        _contentNode = [[MESMinicardContentNode alloc] initWithViewModel:viewModel];
        _contentNode.cornerRadius = 4;
        _contentNode.clipsToBounds = YES;
        [self addSubnode:_contentNode];
    }
    return self;
}

- (void)didLoad {
    [super didLoad];

    self.clipsToBounds = NO;
    self.shadowColor = [UIColor colorWithWhite:0 alpha:0.5].CGColor;
    self.shadowOffset = CGSizeMake(0, 1);
    self.shadowOpacity = 0.5f;
    self.shadowRadius = 1.5f;
    // Shadow performance optimization. See https://github.com/facebook/AsyncDisplayKit/issues/980
    self.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.bounds].CGPath;
}

- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize {
    return [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[_contentNode]];
}

@end

How might you optimize this? Which layers should be rasterised, and which should be layer-backed?

Cheers.

@fatuhoku
Copy link
Author

It looks like besides the network images, there are 5 things that could be stuck onto one layer, and rasterized:

  • recipe title
  • time image
  • time text
  • heart image
  • heart count text

But the stuff living on top of the recipe image there can't really be touched, I don't think?

@vtsoup
Copy link

vtsoup commented Mar 31, 2016

@fatuhoku Without seeing the code for all of your nodes, my guess is that the corner radius and shadow on the cell adds to the slowness. Also, the gradient and avatar subnodes in your content node can be areas to optimize. Again, without seeing the code, this is just a guess. Good luck and thanks for the code from the other thread!

@fatuhoku
Copy link
Author

@vtsoup Thanks — I figure the gradient is probably pretty expensive to draw that's true. I wonder how we can get around it?

And, you're welcome! There's only a handful of LayoutSpecs. It was good fun piecing them together, even though it takes some time.

@gkpetrov89
Copy link

gkpetrov89 commented Apr 20, 2016

@fatuhoku I had a similar problem using layer masks in one of our apps. My solution/workaround was to implement the same visual result using ASDisplayNode and drawing all needed effects.
From the screenshot you provided, the shadow looks the most "expensive" thing to draw. You can try to rasterize all layers and the cell itself. If this does't solve the problem try removing the layer.shadowPath and add another node which mimics the shadow appearance.

@appleguy
Copy link
Contributor

@fatuhoku: Indeed the following 4 properties will seriously impact performance on many devices, as they trigger offscreen rendering which forces the GPU to switch context repeatedly on every frame:

    self.shadowColor = [UIColor colorWithWhite:0 alpha:0.5].CGColor;
    self.shadowOffset = CGSizeMake(0, 1);
    self.shadowOpacity = 0.5f;
    self.shadowRadius = 1.5f;

@EviluS has the right idea here. Drawing the shadow in a more manual way would significantly help. The way that the Paper app achieves these effects is to use thin, stretchable images for each side of the shadow effect (these elements can be layer backed or sometimes even rasterized).

A final thing you should consider is if the UI behavior should be tweaked depending on the performance of a device. For example, the second most expensive thing here is the fade in animations as images load. The iPad 2 is a fairly slow device and the overall user experience may be superior if this particular effect is disabled. If you choose to do this, you would also want to disable it for the iPad 3 (which is slower than the iPad 2 due to the retina display) and also the iPad mini 1.

Apologies for the delay! I'm going to close this issue since, but feel free to reopen for further questions.

@oferRounds
Copy link

oferRounds commented Jul 22, 2017

Hi @appleguy and @EviluS!

I found this discussion after trying to add a shadow to my ASCellNode. In my case, the image is the background of my cell, which features both rounded corners and drop shadows, so I actually apply the effect to the image node itself. So with the first case — achieving rounded corners — I was manage to do ok, using the pre-composited opaque corners technique, like this:

_photoNode.willDisplayNodeContentWithRenderingContext = ^(CGContextRef context, id _Nullable drawParameters) {
            CGRect bounds = CGContextGetClipBoundingBox(context);
            
            UIImage *overlay = [UIImage as_resizableRoundedImageWithCornerRadius:10 cornerColor:[UIColor whiteColor] fillColor:[UIColor clearColor]];
            [overlay drawInRect:bounds];
            
            [[UIBezierPath bezierPathWithRoundedRect:bounds cornerRadius:10] addClip];
        };

But I’m not sure about the way to go with the shadow. @appleguy, how would the stretchable image will look in my case? Not sure about the directions I should give to my designer 🤔

Would love for your help... Thanks!

@gkpetrov89
Copy link

@oferRounds The stretchable image should probably be 1px of width and you should stretch horizontally.

@oferRounds
Copy link

@EviluS missed your comment back then – thanks a lot!

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

No branches or pull requests

5 participants