|
| 1 | +--- |
| 2 | +title: "Frame Drop" |
| 3 | +sidebar_order: 30 |
| 4 | +redirect_from: |
| 5 | + - /product/issues/performance-issues/frame-drop/ |
| 6 | +description: "Learn more about how we detect Frame Drop issues and what you can do to help fix them." |
| 7 | +--- |
| 8 | + |
| 9 | +The main (or UI) thread in a mobile app is responsible for handling all user interaction and needs to be able to respond to gestures and taps in real time. If a long-running operation blocks the main thread, the app becomes unresponsive, impacting the quality of the user experience. |
| 10 | + |
| 11 | +We can detect some specific causes for a main thread stall, like decoding [images](/product/issues/issue-details/performance-issues/image-decoding-main-thread/) or [JSON](/product/issues/issue-details/performance-issues/json-decoding-main-thread/), or [searching strings with regexes](/product/issues/issue-details/performance-issues/regex-main-thread/), but there are many other things that could cause a stall. If a main thread stall causes your app to drop UI frames, but doesn't match one of our specific detectors, Sentry reports it under the generic Frame Drop issue type. |
| 12 | + |
| 13 | +## Detection Criteria |
| 14 | + |
| 15 | +[Profiling](/product/profiling/) must be enabled for Sentry to detect Frame Drop issues. Once set up, Sentry will look for profiles that record a frozen UI frame and then search for the most time-consuming application function call delaying the display link's next vsync. In a run of functions calling through without any self-time, the deepest call will be chosen. |
| 16 | + |
| 17 | +<Note> |
| 18 | + |
| 19 | +The minimum supported version for Cocoa is `8.12.0`. |
| 20 | + |
| 21 | +</Note> |
| 22 | + |
| 23 | +## Function Evidence |
| 24 | + |
| 25 | +To find additional information about your Frame Drop problem, go to its **Issue Details** page and scroll down to the "Function Evidence" section, which shows the following: |
| 26 | + |
| 27 | +- **Transaction Name:** The name of the transaction where the issue was detected. |
| 28 | +- **Suspect Function:** The function that triggered the issue detection. |
| 29 | +- **Duration:** How long the function took to execute and the number of consecutive samples collected by the profiler that contained the function. |
| 30 | + |
| 31 | + |
| 32 | + |
| 33 | +To view the entire profile associated with the issue, click the “View Profile” button. |
| 34 | + |
| 35 | +The profile will indicate where the suspect function was called from, along with other functions being called _by_ the suspect function: |
| 36 | + |
| 37 | + |
| 38 | + |
| 39 | +## Stack Trace |
| 40 | + |
| 41 | +The “Stack Trace” section shows a full stack trace view, highlighting the long-running function frame: |
| 42 | + |
| 43 | + |
| 44 | + |
| 45 | +## Example |
| 46 | + |
| 47 | +### iOS |
| 48 | + |
| 49 | +The following code executes a long-running `while` loop on the main thread: |
| 50 | + |
| 51 | +```objective-c |
| 52 | +// given an array of number strings... |
| 53 | + |
| 54 | +NSMutableOrderedSet<NSString *> *sortedEvenNumbers = [NSMutableOrderedSet<NSString *> orderedSet]; |
| 55 | +[numbers enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger __unused idx, BOOL * _Nonnull __unused stop) { |
| 56 | + if (obj.integerValue % 2 == 0) { |
| 57 | + [sortedEvenNumbers addObject:obj]; |
| 58 | + } |
| 59 | +}]; |
| 60 | +``` |
| 61 | + |
| 62 | +Performance could be improved by moving the long computation off of the main thread. The simplest approach is dispatching it to a lower Quality of Service (QoS) queue (or `NSOperationQueue`): |
| 63 | + |
| 64 | +```objective-c {tabTitle:Dispatch to Background Queue} |
| 65 | +// given an array of number strings... |
| 66 | +
|
| 67 | +NSMutableOrderedSet<NSString *> *sortedEvenNumbers = [NSMutableOrderedSet<NSString *> orderedSet]; |
| 68 | +dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{ |
| 69 | + [numbers enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger __unused idx, BOOL * _Nonnull __unused stop) { |
| 70 | + if (obj.integerValue % 2 == 0) { |
| 71 | + [sortedEvenNumbers addObject:obj]; |
| 72 | + } |
| 73 | + }]; |
| 74 | +}); |
| 75 | +``` |
| 76 | + |
| 77 | +Another avenue to consider for loops is to parallelize iterations. There are several options for doing this: |
| 78 | + |
| 79 | +1. If you're iterating through a Foundation collection, you may already be using `enumerateObjects` or `enumerateKeysAndObjects`. Change this to `-[NSArray|NSSet|NSOrderedSet enumerateObjectsWithOptions:usingBlock:]` or `-[NSDictionary enumerateKeysAndObjectsWithOptions:usingBlock:]`, with option `NSEnumerationConcurrent`. Note that modifying the collection from inside the block will result in an exception being thrown. This is roughly equivalent to dispatching each iteration of the loop body to a concurrent GCD queue. |
| 80 | + |
| 81 | +1. Use `dispatch_apply` to perform iterations of a general loop on a concurrent queue. |
| 82 | + |
| 83 | +```objective-c {tabTitle:Foundation Collection Concurrent Enumeration} |
| 84 | +// given an array of number strings... |
| 85 | +
|
| 86 | +NSMutableOrderedSet<NSString *> *sortedEvenNumbers = [NSMutableOrderedSet<NSString *> orderedSet]; |
| 87 | +[numbers enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(NSString * _Nonnull obj, NSUInteger __unused idx, BOOL * _Nonnull __unused stop) { |
| 88 | + if (obj.integerValue % 2 == 0) { |
| 89 | + [sortedEvenNumbers addObject:obj]; |
| 90 | + } |
| 91 | +}]; |
| 92 | +``` |
| 93 | + |
| 94 | +```objective-c {tabTitle:dispatch_apply} |
| 95 | +// given an array of number strings... |
| 96 | +
|
| 97 | +NSMutableOrderedSet<NSString *> *sortedEvenNumbers = [NSMutableOrderedSet<NSString *> orderedSet]; |
| 98 | +dispatch_apply(numberOfNumbers, dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^(size_t iteration) { |
| 99 | + NSString *number = numbers[iteration]; |
| 100 | + if (number.integerValue % 2 == 0) { |
| 101 | + [sortedEvenNumbers addObject:number]; |
| 102 | + } |
| 103 | +}); |
| 104 | +``` |
| 105 | + |
| 106 | +There are several things to keep in mind when introducing concurrency: |
| 107 | + |
| 108 | +- You may need to `@synchronize` critical sections, use semaphores, or dispatch back to a serial queue (or the main queue for UI work). |
| 109 | +- You may be unable to parallelize loops whose iterations are dependent or where order is significant. |
| 110 | +- Parallelization may be less efficient for small collections because [thread spawning has its own costs](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/AboutThreads/AboutThreads.html#//apple_ref/doc/uid/10000057i-CH6-SW20). So always measure first! |
| 111 | +- Both `enumerateObjects...` and `dispatch_apply` are synchronous and won't return until all iterations have completed. Dispatch their invocation asynchronously off the main queue to avoid waiting. |
0 commit comments