Skip to content

Flutter is only using one CPU Core on certain Android phones (Pixel 8...) for UI, Raster and Isolates #153690

@Almouro

Description

@Almouro

Steps to reproduce

For instance, using a Pixel 8:

  1. have a continuously running animation
  2. have some isolates running with compute(calculation, null)
  3. run a perfetto trace with:
Perfetto command
adb shell perfetto \
  -c - --txt \
  -o /data/misc/perfetto-traces/trace \
<<EOF

buffers: {
    size_kb: 63488
    fill_policy: DISCARD
}
buffers: {
    size_kb: 2048
    fill_policy: DISCARD
}
data_sources: {
    config {
        name: "android.packages_list"
        target_buffer: 1
    }
}
data_sources: {
    config {
        name: "linux.process_stats"
        target_buffer: 1
        process_stats_config {
            scan_all_processes_on_start: true
        }
    }
}
data_sources: {
    config {
        name: "linux.ftrace"
        ftrace_config {
            ftrace_events: "sched/sched_switch"
            ftrace_events: "power/suspend_resume"
            ftrace_events: "sched/sched_wakeup"
            ftrace_events: "sched/sched_wakeup_new"
            ftrace_events: "sched/sched_waking"
            ftrace_events: "sched/sched_process_exit"
            ftrace_events: "sched/sched_process_free"
            ftrace_events: "task/task_newtask"
            ftrace_events: "task/task_rename"
            ftrace_events: "power/cpu_frequency"
            ftrace_events: "power/cpu_idle"
            ftrace_events: "cpu/cpu_topology"
            # atrace_apps: "com.example.flutter_threads"
        }
    }
}

data_sources {
  config {
    name: "track_event"
  }
  producer_name_filter: "com.example.flutter_threads"
}

duration_ms: 10000

EOF

Expected results

Running a perfetto trace should reveal that UI thread, Raster thread and all Isolates should run on separate CPU cores.

Actual results

UI, Raster and DartWorker threads are all using the same CPU core

In Perfetto, hovering over a CPU sched slice reveals that all the slices for my app run on CPU 8
image

Zooming in, we can see that ui, raster and DartWorker are being scheduled on the same thread
image

Cause

flutter/engine#45673 is the PR introducing this change.
Of course, it's a great idea and implementation for most phones to improve the performance of Flutter! 🥳

However, I found 2 issues with this:

  • some phones have only 1 core that gets recognized as performance core
    • for instance the Pixel 8 has 9 cores
      • 1 very fast
      • 4 fast
      • 4 quite slow
  • DartWorkers spawned from the UI thread can't set an affinity (Dart profiler thread can occupy fast core when running on Android. dart-lang/sdk#53748) so by default they will inherit the affinity of the main thread, and also run on the same core
    • even for phones with multiple cores defined as "performance", Isolates will never use all the cores on the phone, only the ones marked as performance
      • for instance, I have a Samsung A10s with 8 cores, including 4 marked as performance. If I spawn 4 isolates, then I might block the UI thread even though I have 4 more cores to use on the phone for background tasks

I think the Flutter team is aware of this already since @jonahwilliams tried a fix here flutter/engine#53136 but I think the impact is a bit more critical especially for apps running several isolates.

I'd be really happy to help with this in any way I can (submitting PRs for instance)! I think:

  1. Dart should indeed have a way to set the CPU affinity, and the Dart workers should be marked as "non performance cores"
  2. If a phone has only 1 big core, we also mark at least the 2nd fastest core as performance, so that UI and raster threads are guaranteed to run on separate cores

Code sample

Code sample
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';

void main() {
  runApp(const MyApp());
}

int complexLoop(int? message) {
  int startTime = DateTime.now().millisecondsSinceEpoch;

  int sum = 0;
  for (int i = 0; i < 1000000000; i++) {
    sum += i;
  }

  int endTime = DateTime.now().millisecondsSinceEpoch;
  print("TIME: ${endTime - startTime}");

  return sum;
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
          floatingActionButton: FloatingActionButton(
            onPressed: () async {
              compute(complexLoop, null);
            },
            tooltip: 'Run calculation',
            child: const Icon(Icons.play_arrow),
          ),
          body: const Center(
            child: FadeSquare(),
          )));
  }
}

class FadeSquare extends StatefulWidget {
  const FadeSquare({super.key});

  @override
  State<FadeSquare> createState() => _FadeSquareState();
}

class _FadeSquareState extends State<FadeSquare>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(milliseconds: 1000),
      vsync: this,
    )..repeat(reverse: true);

    _animation = CurvedAnimation(
      parent: _controller,
      curve: Curves.ease,
    );
  }

  @override
  Widget build(BuildContext context) {
    final width = MediaQuery.sizeOf(context).width;

    return FadeTransition(
      opacity: _animation,
      child: Container(
        width: width,
        height: width,
        color: Colors.green,
      ),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

Flutter Doctor output

Doctor output
[✓] Flutter (Channel stable, 3.24.0, on macOS 14.2.1 23C71 darwin-arm64, locale en-US)
    • Flutter version 3.24.0 on channel stable at /Users/almouro/dev/tools/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 80c2e84975 (3 weeks ago), 2024-07-30 23:06:49 +0700
    • Engine revision b8800d88be
    • Dart version 3.5.0
    • DevTools version 2.37.2

[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
    • Android SDK at /Users/almouro/Library/Android/sdk
    • Platform android-34, build-tools 34.0.0
    • ANDROID_HOME = /Users/almouro/Library/Android/sdk
    • ANDROID_SDK_ROOT = /Users/almouro/Library/Android/sdk
    • Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 17.0.7+0-17.0.7b1000.6-10550314)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 15.4)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 15F31d
    • CocoaPods version 1.15.2

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 2023.1)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 17.0.7+0-17.0.7b1000.6-10550314)

[✓] VS Code (version 1.92.1)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.94.0

[✓] Connected device (3 available)
    • macOS (desktop)                 • macos                 • darwin-arm64   • macOS 14.2.1 23C71 darwin-arm64
    • Mac Designed for iPad (desktop) • mac-designed-for-ipad • darwin         • macOS 14.2.1 23C71 darwin-arm64
    • Chrome (web)                    • chrome                • web-javascript • Google Chrome 127.0.6533.120
    ! Error: Browsing on the local area network for iPhone de Inès Ryder. Ensure the device is unlocked and attached with a cable or associated with the same local area network as this Mac.
      The device must be opted into Developer Mode to connect wirelessly. (code -27)
    ! Error: Browsing on the local area network for Salle de jeux. Ensure the device is unlocked and associated with the same local area network as this Mac. (code -27)
    ! Error: Browsing on the local area network for iPhone de BAM. Ensure the device is unlocked and attached with a cable or associated with the same local area network as this Mac.
      The device must be opted into Developer Mode to connect wirelessly. (code -27)

[✓] Network resources
    • All expected network resources are available.

• No issues found!

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions