diff --git a/AsyncDisplayKit/ASDisplayNode+Beta.h b/AsyncDisplayKit/ASDisplayNode+Beta.h index ca3e2ff0d1..939a003038 100644 --- a/AsyncDisplayKit/ASDisplayNode+Beta.h +++ b/AsyncDisplayKit/ASDisplayNode+Beta.h @@ -18,6 +18,22 @@ void ASPerformBlockOnMainThread(void (^block)()); void ASPerformBlockOnBackgroundThread(void (^block)()); // DISPATCH_QUEUE_PRIORITY_DEFAULT ASDISPLAYNODE_EXTERN_C_END +/** + * Bitmask to indicate what performance measurements the cell should record. + */ +typedef NS_OPTIONS(NSUInteger, ASDisplayNodePerformanceMeasurementOptions) { + ASDisplayNodePerformanceMeasurementOptionLayoutSpec = 1 << 0, + ASDisplayNodePerformanceMeasurementOptionLayoutGeneration = 1 << 1 +}; + +/** + * Keys to retrieve performance entries from the performance dictionary. + */ +extern NSString *const ASDisplayNodeLayoutSpecTotalTimeKey; +extern NSString *const ASDisplayNodeLayoutSpecNumberOfPassesKey; +extern NSString *const ASDisplayNodeLayoutGenerationTotalTimeKey; +extern NSString *const ASDisplayNodeLayoutGenerationNumberOfPassesKey; + @interface ASDisplayNode (Beta) /** @@ -57,6 +73,17 @@ ASDISPLAYNODE_EXTERN_C_END */ @property (nonatomic, copy, nullable) ASDisplayNodeContextModifier didDisplayNodeContentWithRenderingContext; +/** + * @abstract A bitmask representing which actions (layout spec, layout generation) should be measured. + */ +@property (nonatomic, assign) ASDisplayNodePerformanceMeasurementOptions measurementOptions; + +/** + * @abstract A dictionary representing performance measurements collected. + * @note see the constants above to retrieve relevant performance measurements + */ +@property (nonatomic, strong, readonly) NSDictionary *performanceMeasurements; + /** @name Layout Transitioning */ /** diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index dbaf46b8af..6e1dc83a25 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -38,6 +38,11 @@ NSInteger const ASDefaultDrawingPriority = ASDefaultTransactionPriority; NSString * const ASRenderingEngineDidDisplayScheduledNodesNotification = @"ASRenderingEngineDidDisplayScheduledNodes"; NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp = @"ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp"; +NSString * const ASDisplayNodeLayoutSpecTotalTimeKey = @"ASDisplayNodeLayoutSpecTotalTime"; +NSString * const ASDisplayNodeLayoutSpecNumberOfPassesKey = @"ASDisplayNodeLayoutSpecNumberOfPasses"; +NSString * const ASDisplayNodeLayoutGenerationTotalTimeKey = @"ASDisplayNodeLayoutGenerationTotalTime"; +NSString * const ASDisplayNodeLayoutGenerationNumberOfPassesKey = @"ASDisplayNodeLayoutGenerationNumberOfPasses"; + // Forward declare CALayerDelegate protocol as the iOS 10 SDK moves CALayerDelegate from a formal delegate to a protocol. // We have to forward declare the protocol as this place otherwise it will not compile compiling with an Base SDK < iOS 10 @@ -1156,6 +1161,33 @@ ASLayoutableSizeHelperForwarding // subclass override } +- (void)setMeasurementOptions:(ASDisplayNodePerformanceMeasurementOptions)measurementOptions +{ + ASDN::MutexLocker l(__instanceLock__); + _measurementOptions = measurementOptions; +} + +- (ASDisplayNodePerformanceMeasurementOptions)measurementOptions +{ + ASDN::MutexLocker l(__instanceLock__); + return _measurementOptions; +} + +- (NSDictionary *)performanceMeasurements +{ + ASDN::MutexLocker l(__instanceLock__); + NSMutableDictionary *measurements = [NSMutableDictionary dictionaryWithCapacity:4]; + if (_measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutSpec) { + measurements[ASDisplayNodeLayoutSpecTotalTimeKey] = @(_layoutSpecTotalTime); + measurements[ASDisplayNodeLayoutSpecNumberOfPassesKey] = @(_layoutSpecNumberOfPasses); + } + if (_measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutGeneration) { + measurements[ASDisplayNodeLayoutGenerationTotalTimeKey] = @(_layoutGenerationTotalTime); + measurements[ASDisplayNodeLayoutGenerationNumberOfPassesKey] = @(_layoutGenerationNumberOfPasses); + } + return measurements; +} + #pragma mark - Asynchronous display - (BOOL)displaysAsynchronously @@ -2343,8 +2375,16 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) ASDN::MutexLocker l(__instanceLock__); if ((_methodOverrides & ASDisplayNodeMethodOverrideLayoutSpecThatFits) || _layoutSpecBlock != NULL) { - ASLayoutSpec *layoutSpec = [self layoutSpecThatFits:constrainedSize]; - + ASLayoutSpec *layoutSpec = nil; + // optional performance measurement for cell nodes + if (_measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutSpec) { + ASDN::SumScopeTimer t(_layoutSpecTotalTime); + _layoutSpecNumberOfPasses++; + layoutSpec = [self layoutSpecThatFits:constrainedSize]; + } else { + layoutSpec = [self layoutSpecThatFits:constrainedSize]; + } + ASDisplayNodeAssert(layoutSpec.isMutable, @"Node %@ returned layout spec %@ that has already been used. Layout specs should always be regenerated.", self, layoutSpec); layoutSpec.parent = self; // This causes upward propogation of any non-default layoutable values. @@ -2353,7 +2393,16 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) ASEnvironmentStatePropagateDown(layoutSpec, self.environmentTraitCollection); layoutSpec.isMutable = NO; - ASLayout *layout = [layoutSpec layoutThatFits:constrainedSize]; + ASLayout *layout = nil; + // optional performance measurement for cell nodes + if (_measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutGeneration) { + ASDN::SumScopeTimer t(_layoutGenerationTotalTime); + _layoutGenerationNumberOfPasses++; + layout = [layoutSpec layoutThatFits:constrainedSize]; + } else { + layout = [layoutSpec layoutThatFits:constrainedSize]; + } + ASDisplayNodeAssertNotNil(layout, @"[ASLayoutSpec measureWithSizeRange:] should never return nil! %@, %@", self, layoutSpec); // Make sure layoutableObject of the root layout is `self`, so that the flattened layout will be structurally correct. diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index 4cd852fe5f..a3a609e6eb 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -22,6 +22,8 @@ #import "ASEnvironment.h" #import "ASObjectDescriptionHelpers.h" +#import "ASDisplayNode+Beta.h" + @protocol _ASDisplayLayerDelegate; @class _ASDisplayLayer; @class _ASPendingState; @@ -163,6 +165,13 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo CGPoint _accessibilityActivationPoint; UIBezierPath *_accessibilityPath; + // performance measurement + ASDisplayNodePerformanceMeasurementOptions _measurementOptions; + NSTimeInterval _layoutSpecTotalTime; + NSUInteger _layoutSpecNumberOfPasses; + NSTimeInterval _layoutGenerationTotalTime; + NSUInteger _layoutGenerationNumberOfPasses; + #if TIME_DISPLAYNODE_OPS @public NSTimeInterval _debugTimeToCreateView; diff --git a/AsyncDisplayKit/Private/_ASScopeTimer.h b/AsyncDisplayKit/Private/_ASScopeTimer.h index 04d63be0bf..456b2aefb6 100644 --- a/AsyncDisplayKit/Private/_ASScopeTimer.h +++ b/AsyncDisplayKit/Private/_ASScopeTimer.h @@ -37,4 +37,16 @@ namespace ASDN { outT = CACurrentMediaTime() - begin; } }; + + // variant where repeated calls are summed + struct SumScopeTimer { + NSTimeInterval begin; + NSTimeInterval &outT; + SumScopeTimer(NSTimeInterval &outRef) : outT(outRef) { + begin = CACurrentMediaTime(); + } + ~SumScopeTimer() { + outT += CACurrentMediaTime() - begin; + } + }; }