The purpose of this article

  • This section describes application fluency detection and optimization strategies
  • This section describes memory detection and optimization policies
  • This section describes the meaning and process of performance optimization proof
  • This section describes how to use Observatory, a performance monitoring tool

The directory structure

  • fluency
  • Memory optimization
  • Optimization prove
  • Observatory is used on a basic basis
  • conclusion

fluency

The key metrics for App fluency are UI frame rate, GPU frame rate, and we expect it to be 60fps, which is 16ms per frame.

Run in profile/release mode

To get the data closest to the production environment, we should choose a real machine as low end as possible and run the app in Profile mode or Release mode.

  1. Debug mode does some extra checking, for exampleassert()Etc.
  2. To speed up development, Dart code is compiled in Just in Time (JIT) mode, while profiles and Releases are compiled Ahead Of time to machine code AOT (Ahead Of time), so debug is much slower
  1. Dart in Profile Mode in Android Studio and IntelliJ, on the menu bar Run > Flutter Run main.dart in Profile Mode

  2. VS Code: Open the launch.json file and set flutterMode to profile:

"configurations": [{"name": "Flutter"."request": "launch"."type": "dart"."flutterMode": "profile" Remember to change it back after the test!}]Copy the code
  1. Start with command line:
$ flutter run --profile
Copy the code

Test frame rate

So what are some ways to detect frame rates? Flutter provides us with Performance Overlay, as shown below, with green representing the current render frame.

We have three ways to open it

  1. In Android Studio and IntelliJ IDEA: SelectView > Tool Windows > Flutter InspectorClick the button below.

  1. In VS Code, select View > Command Palette… A Command panel displays. Enter Performance in the command panel and select Toggle Performance Overlay If the command displays as unavailable, you need to check whether the app is running.

  2. Run the keyboard from the command line and type P

  3. Set the showPerformanceOverlay property to true in the MaterialApp or WidgetsApp constructor:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      showPerformanceOverlay: true./ / open
      title: 'My Awesome App',
      home: MyHomePage(title: 'My Awesome App')); }}Copy the code

Then it’s a matter of manipulating the app and seeing if there’s a red line on the chart. Green represents the current frame, and the graph is constantly drawn as the page changes. There are 2 charts on the mask, each chart has 3 bars, each bar represents 16ms. If most frames are in the first frame, the desired frame rate has been achieved.

The graph shows the UI frame rate and GPU frame rate respectively. If it appears red, the corresponding thread has too much work to do. Let’s take a look at what the four main threads of Flutter are responsible for.

  • Platform thread: The thread in which the plug-in code runs; The main thread for Android/iOS,
  • UI thread: Dart code is executed in the Dart VIRTUAL machine. The idea is to create a view tree and send it to the GPU. Be careful not to block this thread!
  • GPU thread: Render the view tree mentioned above. Although we do not have direct access to THE GPU thread and data in Flutter, Dart code may cause this thread to slow down
  • I/O thread: Performs time-consuming tasks

In the process of running the APP, observe the places of explosion and trigger scenes and analyze them.

Analysis methods

  • If the UI is red: maybe some time-consuming function was executed? Or too many function calls? High algorithm complexity?
  • If only the GPU is red: maybe the graphics to be drawn are too complex? Or too many GPU operations?
    • For example, to achieve the translucent effect of a blending layer: if the opacity is set to the top layer control, the CPU will render each child control layer before executing itsaveLayerSave the action as a layer, and finally set the opacity of this layer. whilesaveLayerIt’s expensive, and here’s an official tip: First check whether the effects are really necessary; If necessary, we can set transparency to each child control instead of the parent control. The cropping operation is similar.
    • Another thing that slows down GPU rendering is not caching static images, which results in redrawing every build. We can add the static graph to theRepaintBoundryControl, the engine will automatically determine whether the image is complex enough to require repaint boundary and ignore it if it is not.
    • Enable saveLayer and graphics cache checks
    MaterialApp(
        showPerformanceOverlay: true,
        checkerboardOffscreenLayers: true.// Graphics using saveLayer are displayed in checkerboard format and flash as the page refreshes
        checkerboardRasterCacheImages: true.// Cached static images do not change the checkerboard color when refreshing the page; If the checkerboard color changes, it's being recached, which is what we want to avoid...). ;Copy the code

Strategies to improve fluency

  • Can the code call time be delayed? Like a bottom navigation bar Page, it is not necessary to create each sub-page the first time you enter
  • Try to refresh locally
  • Put time-consuming calculations into a separate isolate to perform
  • Check for unnecessary saveLayer
  • Check whether static images are cached
  • Relayout boundary: reference
  • Repaint boundary: reference

Memory optimization

In terms of memory optimization, our goal is to reduce the application memory footprint, reduce the probability of being killed by the system, and at the same time avoid memory leaks and reduce memory fragmentation as much as possible.

Memory optimization strategy

  • Loading objects too large? If the image quality and size do not limit the load
  • Loading too many objects? Such as loading a long list; Create objects in frequently called methods
    • Set the cache size/length appropriately
    • Clear cached data when out of memory or when off the page
    • Use listView.build () to reuse the child control
    • Avoid creating objects in onDraw or setting the same parameters in custom drawing
    • Reuse resources provided by the system, such as strings, images, animations, styles, colors, and simple layouts, directly referenced in the application
  • Memory leak problem? Dispose A listener to be disposed
  • Are invisible views also in build?
  • Is the network request canceled after the page leaves?

How do I get memory state

Dart provides Observatory, a performance measurement tool that I’ll cover in detail in the final section

Optimization prove

Meaning of optimization proof

Performance optimization, unlike other development requirements that require functionality to be completed, requires statistics and data to prove the effectiveness of the optimization. Like how much frame rate has gone up? How much CPU usage has been reduced? How much memory footprint is reduced? Compared with other optimization strategies, which optimization effect is better?

Optimize the proof process

For example

Take checking for fluency, for example

1. Run and enable Performance Overlay in profile mode to test the app as a whole

2. Locate the module whose frame rate is in red

3. Isolate the page and measure it several times to get baseline frame rate data. If a long list page gets stuck, we can write a performance test of ListView sliding using TestDriver (see more)Flutter gallery)

scroll_pref.dart

void main() {
  enableFlutterDriverExtension();
  runApp(const GalleryApp(testMode: true));
}
Copy the code

scroll_perf_test.dart

void main() {
  group('scrolling performance test', () {
    FlutterDriver driver;

    setUpAll(() async {
      driver = await FlutterDriver.connect();
    });

    tearDownAll(() async {
      if(driver ! =null)
        driver.close();
    });

    test('measure', () async {
      final Timeline timeline = await driver.traceAction(() async {
        await driver.tap(find.text('Material'));

        final SerializableFinder demoList = find.byValueKey('GalleryDemoList');

        for (int i = 0; i < 5; i++) {
          await driver.scroll(demoList, 0.0.300.0.const Duration(milliseconds: 300));
          await Future<void>.delayed(const Duration(milliseconds: 500));
        }

        // Scroll up
        for (int i = 0; i < 5; i++) {
          await driver.scroll(demoList, 0.0.300.0.const Duration(milliseconds: 300));
          await Future<void>.delayed(const Duration(milliseconds: 500));
        }
      });

      TimelineSummary.summarize(timeline)
        ..writeSummaryToFile('home_scroll_perf', pretty: true)
        ..writeTimelineToFile('home_scroll_perf', pretty: true);
    });
  });
}
Copy the code

Run the following command on the cli

flutter driver --target=test_driver/scroll_perf.dart 
Copy the code

This command will:

  • Build the target app and install it on the device
  • Run intest_driver/In the directoryscroll_perf_test.dartThe Flutter Drive can help you find the belt_testFile with the same name suffix)

The Test Driver will install the APP on the device, jump to the Material-GalleryDemoList page and do 5 sliding list operations. After the TimelineSummary is executed, two JSON files home_scroll_perf.timeline.json and home_scroll_perf.timeline_summary.json are generated in the build directory. Here’s a look at the contents of the timeline_summary.json file

{"average_frame_build_time_millis": 5.6319655172413805, # percentiLE_frame_build_time_millis ": 10.216, "99 th_percentile_frame_build_time_millis" : 17.168, "worst_frame_build_time_millis" : # missed_frame_build_budget_count": 21, # average_frame_rasterizer_time_millis: # average rasterization time per frame "90th_percentiLE_frame_rasterizer_time_millis ": 22.338, "99 th_percentile_frame_rasterizer_time_millis" : 42.661, "worst_frame_rasterizer_time_millis" : 43.161, "missed_FRAME_rasterizer_BUDGEt_count ": 112," FRAME_count ": 116, "frame_build_times": [...] ,# frame_rasterizer_times: [...] # Rasterization time for all frames}Copy the code

4. The optimization

5. Measure again with the method of Step 3, and get the exact optimization effect by comparing the baseline

Performance debugging API provided by Flutter

  • Measure application startup time
  • Measure the execution time of a code segment

See the official documentation for more information

Observatory, a performance monitoring tool

Observatory is a tool for analyzing and debugging Dart applications. Observatory allows you to view a running Dart Virtual machine (VM) on demand and provide real-time, real-time data reporting. You can use it to browse many states of your application.

Open the Observatory

There are two ways:

  1. Open it in androidStudioFlutter InspectorPanel, click the small alarm clock icon, as shown below
  2. Run it on the command lineflutter runAfter the application is successfully started, a URL is displayed in the command line interface. You only need to copy the URL to the browser.

To open the Observatory panel, select ISOLATE to indicate the current application.

The main page

Here are some of the pages that performance tuning often focuses on.

1. CPU Profile

Where is app time spent?

It usually takes a few seconds to load the page, so be patient. At the bottom of the chart is a table by CPU usage, reflecting the number of calls to the function and the execution time (highlighted). In general, the first functions (what are these functions? We didn’t write the Dart code. If you find yourself with a disproportionate number of function calls, you may have a problem.

Note: The CPU profile of the Flutter application is not the same as the data displayed in the official documentation. There are no VM tags, so the exact meaning of the percentage needs to be studied.

Sampling process: It samples the ISOLATE at regular intervals. The sampled data is stored in a circular buffer (called a profile), which holds about 2 minutes of data. Once the buffer is full, it replaces the oldest one with the newest one.

  • Profile Contains: Sampling duration and number of samples
  • Sampling: Sampling frequency, default: 1000Hz, that is, Sampling every millisecond

2. Allocation Profile

Who ate the memory?

The Heap, the space in memory where dynamically allocated Dart objects reside

  • New generation: newly created objects. Generally, objects are small and have a short life cycle, such as local variables. There is a lot of GC activity here
  • Old Generation: The New generation that survives the GC will be promoted to the Old generation, which has more space than the New generation and is more suitable for large objects and objects with long life cycles

From this panel you can see the memory size and ratio of the new generation/old generation; The amount of memory used by each type.

For the convenience of debug, we can obtain the memory allocation status of a certain period of time: click Reset Accumulator button to clear data, execute the program to be tested, and click refresh.

To check for memory leaks, we can manually perform GC by clicking the GC button.

Accumulator Instances: This is the number of Accumulator Instances since Reset Accumulator. Current Instances: number of Current objects

3. Heap Map

Whether memory fragmentation occurs

The Heap Map panel allows you to view memory status in old Generation

It shows blocks of memory in color. Each page of memory is 256 KB, separated by a horizontal black line. The color of the pixel indicates the class ID of the object – for example, blue indicates a string and green indicates a double precision table. The available space is white, and the instructions (code) are purple. If garbage collection is started (using the GC button on the Allocate Profile screen), more empty areas (free space) are shown in the heap map. When you hover the cursor over it, the status bar at the top displays information about the object represented by the pixel below the cursor. The information displayed includes the type, size, and address of the object. When you see a lot of other colors scattered in the white area, it indicates memory fragmentation, possibly caused by memory leaks.

other

1. Code Coverage

Know what code executes and what doesn’t

  • Green: Executed code
  • Red: code not executed
  • No color: Unexecutable code

Application scenario: Write a unit test of a class. After running the test, you can check which code is not covered, and then complete the test

2. The Class/Instance information

View the state of an instance, such as our project using Flutter_redux, the page’s source and state tree, and when the page has an unexpected effect, we can view the state tree through the Observatory

For example

Observatory helped me find the real culprit of the loop call

conclusion

Performance optimization involves many methods of application and is difficult to summarize in a single word. In this article we focus on the two main topics of performance optimization — fluency and memory optimization, and introduce their detection methods and optimization strategies respectively. In addition, while we are optimizing, we should also strengthen the proof of optimization and use data to speak. Finally, I highly recommend that you try Observatory, if you have a strange problem in development, it may help you find the answer.

reference

  • Observatory
  • Flutter Debugging is a common method
  • Flutter Performance Profiling

The copyright of this article belongs to Zaihui RESEARCH and development team, welcome to reprint, reprint please reserve source. @akindone