Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 8ccc8a6

Browse files
author
Jonah Williams
authored
[Android] Add support for setting thread affinity based on core speed. (#45673)
flutter/flutter#134452 This patch parses the speed of all CPU data out of /proc and constructs a table that allows us to request high level CPU affinities: performance, efficiency, and not performance. These affinties are applied where appropriate during Android thread construction.
1 parent 64705ac commit 8ccc8a6

File tree

9 files changed

+335
-0
lines changed

9 files changed

+335
-0
lines changed

ci/licenses_golden/excluded_files

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
../../../flutter/fml/closure_unittests.cc
8787
../../../flutter/fml/command_line_unittest.cc
8888
../../../flutter/fml/container_unittests.cc
89+
../../../flutter/fml/cpu_affinity_unittests.cc
8990
../../../flutter/fml/endianness_unittests.cc
9091
../../../flutter/fml/file_unittest.cc
9192
../../../flutter/fml/hash_combine_unittests.cc

ci/licenses_golden/licenses_flutter

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -870,6 +870,8 @@ ORIGIN: ../../../flutter/fml/concurrent_message_loop.cc + ../../../flutter/LICEN
870870
ORIGIN: ../../../flutter/fml/concurrent_message_loop.h + ../../../flutter/LICENSE
871871
ORIGIN: ../../../flutter/fml/concurrent_message_loop_factory.cc + ../../../flutter/LICENSE
872872
ORIGIN: ../../../flutter/fml/container.h + ../../../flutter/LICENSE
873+
ORIGIN: ../../../flutter/fml/cpu_affinity.cc + ../../../flutter/LICENSE
874+
ORIGIN: ../../../flutter/fml/cpu_affinity.h + ../../../flutter/LICENSE
873875
ORIGIN: ../../../flutter/fml/dart/dart_converter.cc + ../../../flutter/LICENSE
874876
ORIGIN: ../../../flutter/fml/dart/dart_converter.h + ../../../flutter/LICENSE
875877
ORIGIN: ../../../flutter/fml/delayed_task.cc + ../../../flutter/LICENSE
@@ -916,6 +918,8 @@ ORIGIN: ../../../flutter/fml/message_loop_task_queues_benchmark.cc + ../../../fl
916918
ORIGIN: ../../../flutter/fml/native_library.h + ../../../flutter/LICENSE
917919
ORIGIN: ../../../flutter/fml/paths.cc + ../../../flutter/LICENSE
918920
ORIGIN: ../../../flutter/fml/paths.h + ../../../flutter/LICENSE
921+
ORIGIN: ../../../flutter/fml/platform/android/cpu_affinity.cc + ../../../flutter/LICENSE
922+
ORIGIN: ../../../flutter/fml/platform/android/cpu_affinity.h + ../../../flutter/LICENSE
919923
ORIGIN: ../../../flutter/fml/platform/android/jni_util.cc + ../../../flutter/LICENSE
920924
ORIGIN: ../../../flutter/fml/platform/android/jni_util.h + ../../../flutter/LICENSE
921925
ORIGIN: ../../../flutter/fml/platform/android/jni_weak_ref.cc + ../../../flutter/LICENSE
@@ -3614,6 +3618,8 @@ FILE: ../../../flutter/fml/concurrent_message_loop.cc
36143618
FILE: ../../../flutter/fml/concurrent_message_loop.h
36153619
FILE: ../../../flutter/fml/concurrent_message_loop_factory.cc
36163620
FILE: ../../../flutter/fml/container.h
3621+
FILE: ../../../flutter/fml/cpu_affinity.cc
3622+
FILE: ../../../flutter/fml/cpu_affinity.h
36173623
FILE: ../../../flutter/fml/dart/dart_converter.cc
36183624
FILE: ../../../flutter/fml/dart/dart_converter.h
36193625
FILE: ../../../flutter/fml/delayed_task.cc
@@ -3660,6 +3666,8 @@ FILE: ../../../flutter/fml/message_loop_task_queues_benchmark.cc
36603666
FILE: ../../../flutter/fml/native_library.h
36613667
FILE: ../../../flutter/fml/paths.cc
36623668
FILE: ../../../flutter/fml/paths.h
3669+
FILE: ../../../flutter/fml/platform/android/cpu_affinity.cc
3670+
FILE: ../../../flutter/fml/platform/android/cpu_affinity.h
36633671
FILE: ../../../flutter/fml/platform/android/jni_util.cc
36643672
FILE: ../../../flutter/fml/platform/android/jni_util.h
36653673
FILE: ../../../flutter/fml/platform/android/jni_weak_ref.cc

fml/BUILD.gn

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ source_set("fml") {
1919
"concurrent_message_loop.cc",
2020
"concurrent_message_loop.h",
2121
"container.h",
22+
"cpu_affinity.cc",
23+
"cpu_affinity.h",
2224
"delayed_task.cc",
2325
"delayed_task.h",
2426
"eintr_wrapper.h",
@@ -170,6 +172,8 @@ source_set("fml") {
170172

171173
if (is_android) {
172174
sources += [
175+
"platform/android/cpu_affinity.cc",
176+
"platform/android/cpu_affinity.h",
173177
"platform/android/jni_util.cc",
174178
"platform/android/jni_util.h",
175179
"platform/android/jni_weak_ref.cc",
@@ -322,6 +326,7 @@ if (enable_unittests) {
322326
"closure_unittests.cc",
323327
"command_line_unittest.cc",
324328
"container_unittests.cc",
329+
"cpu_affinity_unittests.cc",
325330
"endianness_unittests.cc",
326331
"file_unittest.cc",
327332
"hash_combine_unittests.cc",

fml/cpu_affinity.cc

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#include "flutter/fml/cpu_affinity.h"
6+
7+
#include <fstream>
8+
#include <optional>
9+
#include <string>
10+
11+
namespace fml {
12+
13+
CPUSpeedTracker::CPUSpeedTracker(std::vector<CpuIndexAndSpeed> data)
14+
: cpu_speeds_(std::move(data)) {
15+
std::optional<int64_t> max_speed = std::nullopt;
16+
std::optional<int64_t> min_speed = std::nullopt;
17+
for (const auto& data : cpu_speeds_) {
18+
if (!max_speed.has_value() || data.speed > max_speed.value()) {
19+
max_speed = data.speed;
20+
}
21+
if (!min_speed.has_value() || data.speed < min_speed.value()) {
22+
min_speed = data.speed;
23+
}
24+
}
25+
if (!max_speed.has_value() || !min_speed.has_value() ||
26+
min_speed.value() == max_speed.value()) {
27+
return;
28+
}
29+
30+
for (const auto& data : cpu_speeds_) {
31+
if (data.speed == max_speed.value()) {
32+
performance_.push_back(data.index);
33+
} else {
34+
not_performance_.push_back(data.index);
35+
}
36+
if (data.speed == min_speed.value()) {
37+
efficiency_.push_back(data.index);
38+
}
39+
}
40+
41+
valid_ = true;
42+
}
43+
44+
bool CPUSpeedTracker::IsValid() const {
45+
return valid_;
46+
}
47+
48+
const std::vector<size_t>& CPUSpeedTracker::GetIndices(
49+
CpuAffinity affinity) const {
50+
switch (affinity) {
51+
case CpuAffinity::kPerformance:
52+
return performance_;
53+
case CpuAffinity::kEfficiency:
54+
return efficiency_;
55+
case CpuAffinity::kNotPerformance:
56+
return not_performance_;
57+
}
58+
}
59+
60+
// Get the size of the cpuinfo file by reading it until the end. This is
61+
// required because files under /proc do not always return a valid size
62+
// when using fseek(0, SEEK_END) + ftell(). Nor can they be mmap()-ed.
63+
std::optional<int64_t> ReadIntFromFile(const std::string& path) {
64+
// size_t data_length = 0u;
65+
std::ifstream file;
66+
file.open(path.c_str());
67+
68+
// Dont use stoi because if this data isnt a parseable number then it
69+
// will abort, as we compile with exceptions disabled.
70+
int64_t speed = 0;
71+
file >> speed;
72+
if (speed > 0) {
73+
return speed;
74+
}
75+
return std::nullopt;
76+
}
77+
78+
} // namespace fml

fml/cpu_affinity.h

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#pragma once
6+
7+
#include <optional>
8+
#include <string>
9+
#include <vector>
10+
11+
namespace fml {
12+
13+
/// The CPU Affinity provides a hint to the operating system on which cores a
14+
/// particular thread should be scheduled on. The operating system may or may
15+
/// not honor these requests.
16+
enum class CpuAffinity {
17+
/// @brief Request CPU affinity for the performance cores.
18+
///
19+
/// Generally speaking, only the UI and Raster thread should
20+
/// use this option.
21+
kPerformance,
22+
23+
/// @brief Request CPU affinity for the efficiency cores.
24+
kEfficiency,
25+
26+
/// @brief Request affinity for all non-performance cores.
27+
kNotPerformance,
28+
};
29+
30+
struct CpuIndexAndSpeed {
31+
// The index of the given CPU.
32+
size_t index;
33+
// CPU speed in kHZ
34+
int64_t speed;
35+
};
36+
37+
/// @brief A class that computes the correct CPU indices for a requested CPU
38+
/// affinity.
39+
///
40+
/// @note This is visible for testing.
41+
class CPUSpeedTracker {
42+
public:
43+
explicit CPUSpeedTracker(std::vector<CpuIndexAndSpeed> data);
44+
45+
/// @brief The class is valid if it has more than one CPU index and a distinct
46+
/// set of efficiency or performance CPUs.
47+
///
48+
/// If all CPUs are the same speed this returns false, and all requests
49+
/// to set affinity are ignored.
50+
bool IsValid() const;
51+
52+
/// @brief Return the set of CPU indices for the requested CPU affinity.
53+
///
54+
/// If the tracker is valid, this will always return a non-empty set.
55+
const std::vector<size_t>& GetIndices(CpuAffinity affinity) const;
56+
57+
private:
58+
bool valid_ = false;
59+
std::vector<CpuIndexAndSpeed> cpu_speeds_;
60+
std::vector<size_t> efficiency_;
61+
std::vector<size_t> performance_;
62+
std::vector<size_t> not_performance_;
63+
};
64+
65+
/// @note Visible for testing.
66+
std::optional<int64_t> ReadIntFromFile(const std::string& path);
67+
68+
} // namespace fml

fml/cpu_affinity_unittests.cc

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#include "cpu_affinity.h"
6+
7+
#include "fml/file.h"
8+
#include "fml/mapping.h"
9+
#include "gtest/gtest.h"
10+
#include "logging.h"
11+
12+
namespace fml {
13+
namespace testing {
14+
15+
TEST(CpuAffinity, NormalSlowMedFastCores) {
16+
auto speeds = {CpuIndexAndSpeed{.index = 0, .speed = 1},
17+
CpuIndexAndSpeed{.index = 1, .speed = 2},
18+
CpuIndexAndSpeed{.index = 2, .speed = 3}};
19+
auto tracker = CPUSpeedTracker(speeds);
20+
21+
ASSERT_TRUE(tracker.IsValid());
22+
ASSERT_EQ(tracker.GetIndices(CpuAffinity::kEfficiency)[0], 0u);
23+
ASSERT_EQ(tracker.GetIndices(CpuAffinity::kPerformance)[0], 2u);
24+
ASSERT_EQ(tracker.GetIndices(CpuAffinity::kNotPerformance).size(), 2u);
25+
ASSERT_EQ(tracker.GetIndices(CpuAffinity::kNotPerformance)[0], 0u);
26+
ASSERT_EQ(tracker.GetIndices(CpuAffinity::kNotPerformance)[1], 1u);
27+
}
28+
29+
TEST(CpuAffinity, NoCpuData) {
30+
auto tracker = CPUSpeedTracker({});
31+
32+
ASSERT_FALSE(tracker.IsValid());
33+
}
34+
35+
TEST(CpuAffinity, AllSameSpeed) {
36+
auto speeds = {CpuIndexAndSpeed{.index = 0, .speed = 1},
37+
CpuIndexAndSpeed{.index = 1, .speed = 1},
38+
CpuIndexAndSpeed{.index = 2, .speed = 1}};
39+
auto tracker = CPUSpeedTracker(speeds);
40+
41+
ASSERT_FALSE(tracker.IsValid());
42+
}
43+
44+
TEST(CpuAffinity, SingleCore) {
45+
auto speeds = {CpuIndexAndSpeed{.index = 0, .speed = 1}};
46+
auto tracker = CPUSpeedTracker(speeds);
47+
48+
ASSERT_FALSE(tracker.IsValid());
49+
}
50+
51+
TEST(CpuAffinity, FileParsing) {
52+
fml::ScopedTemporaryDirectory base_dir;
53+
ASSERT_TRUE(base_dir.fd().is_valid());
54+
55+
// Generate a fake CPU speed file
56+
fml::DataMapping test_data(std::string("12345"));
57+
ASSERT_TRUE(fml::WriteAtomically(base_dir.fd(), "test_file", test_data));
58+
59+
auto file = fml::OpenFileReadOnly(base_dir.fd(), "test_file");
60+
ASSERT_TRUE(file.is_valid());
61+
62+
// Open file and parse speed.
63+
auto result = ReadIntFromFile(base_dir.path() + "/test_file");
64+
ASSERT_TRUE(result.has_value());
65+
ASSERT_EQ(result.value_or(0), 12345);
66+
}
67+
68+
TEST(CpuAffinity, FileParsingWithNonNumber) {
69+
fml::ScopedTemporaryDirectory base_dir;
70+
ASSERT_TRUE(base_dir.fd().is_valid());
71+
72+
// Generate a fake CPU speed file
73+
fml::DataMapping test_data(std::string("whoa this isnt a number"));
74+
ASSERT_TRUE(fml::WriteAtomically(base_dir.fd(), "test_file", test_data));
75+
76+
auto file = fml::OpenFileReadOnly(base_dir.fd(), "test_file");
77+
ASSERT_TRUE(file.is_valid());
78+
79+
// Open file and parse speed.
80+
auto result = ReadIntFromFile(base_dir.path() + "/test_file");
81+
ASSERT_FALSE(result.has_value());
82+
}
83+
84+
TEST(CpuAffinity, MissingFileParsing) {
85+
auto result = ReadIntFromFile("/does_not_exist");
86+
ASSERT_FALSE(result.has_value());
87+
}
88+
89+
} // namespace testing
90+
} // namespace fml
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#include "flutter/fml/platform/android/cpu_affinity.h"
6+
7+
#include <pthread.h>
8+
#include <sys/resource.h>
9+
#include <sys/time.h>
10+
#include <unistd.h>
11+
#include <mutex>
12+
#include <optional>
13+
#include <thread>
14+
15+
namespace fml {
16+
17+
/// The CPUSpeedTracker is initialized once the first time a thread affinity is
18+
/// requested.
19+
std::once_flag gCPUTrackerFlag;
20+
static CPUSpeedTracker* gCPUTracker;
21+
22+
// For each CPU index provided, attempts to open the file
23+
// /sys/devices/system/cpu/cpu$NUM/cpufreq/cpuinfo_max_freq and parse a number
24+
// containing the CPU frequency.
25+
void InitCPUInfo(size_t cpu_count) {
26+
std::vector<CpuIndexAndSpeed> cpu_speeds;
27+
28+
for (auto i = 0u; i < cpu_count; i++) {
29+
auto path = "/sys/devices/system/cpu/cpu" + std::to_string(i) +
30+
"/cpufreq/cpuinfo_max_freq";
31+
auto speed = ReadIntFromFile(path);
32+
if (speed.has_value()) {
33+
cpu_speeds.push_back({.index = i, .speed = speed.value()});
34+
}
35+
}
36+
gCPUTracker = new CPUSpeedTracker(cpu_speeds);
37+
}
38+
39+
bool RequestAffinity(CpuAffinity affinity) {
40+
// Populate CPU Info if uninitialized.
41+
auto count = std::thread::hardware_concurrency();
42+
std::call_once(gCPUTrackerFlag, [count]() { InitCPUInfo(count); });
43+
if (gCPUTracker == nullptr) {
44+
return true;
45+
}
46+
47+
if (!gCPUTracker->IsValid()) {
48+
return true;
49+
}
50+
51+
cpu_set_t set;
52+
CPU_ZERO(&set);
53+
for (const auto index : gCPUTracker->GetIndices(affinity)) {
54+
CPU_SET(index, &set);
55+
}
56+
return sched_setaffinity(gettid(), sizeof(set), &set) == 0;
57+
}
58+
59+
} // namespace fml
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#pragma once
6+
7+
#include "flutter/fml/cpu_affinity.h"
8+
9+
namespace fml {
10+
11+
/// @brief Request the given affinity for the current thread.
12+
///
13+
/// Returns true if successfull, or if it was a no-op. This function is
14+
/// only supported on Android devices.
15+
///
16+
/// Affinity requests are based on documented CPU speed. This speed data
17+
/// is parsed from cpuinfo_max_freq files, see also:
18+
/// https://www.kernel.org/doc/Documentation/cpu-freq/user-guide.txt
19+
bool RequestAffinity(CpuAffinity affinity);
20+
21+
} // namespace fml

0 commit comments

Comments
 (0)