Skip to content

Commit a5bd9ca

Browse files
armcknightlizokm
andauthored
docs: add page for long tasks causing frame drops (#7625)
Co-authored-by: Liza Mock <liza.mock@sentry.io>
1 parent 2ac1631 commit a5bd9ca

File tree

4 files changed

+111
-0
lines changed

4 files changed

+111
-0
lines changed
Loading
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
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+
![Frame Drop Function Evidence](frame-drop-function-evidence.png)
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+
![Frame Drop Profile](frame-drop-profile.png)
38+
39+
## Stack Trace
40+
41+
The “Stack Trace” section shows a full stack trace view, highlighting the long-running function frame:
42+
43+
![Frame Drop Stack Trace](frame-drop-stack-trace.png)
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

Comments
 (0)