// Copyright 2021 The libgav1 Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "src/utils/cpu.h" #if defined(__linux__) #include #include #include #include #include #include #endif // defined(__linux__) #include "gtest/gtest.h" #include "src/utils/logging.h" namespace libgav1 { namespace { #if defined(__linux__) // Sample code for getting the number of performance CPU cores. The following // sources were consulted: // * https://www.kernel.org/doc/html/latest/admin-guide/cputopology.html // * cpu-hotplug.txt: CPU hotplug Support in Linux(tm) Kernel // https://lwn.net/Articles/537570/ // * https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-devices-system-cpu // * Android bionic source code of get_nprocs(): // libc/bionic/sysinfo.cpp // * glibc 2.30 source code of get_nprocs(): // sysdeps/unix/sysv/linux/getsysstats.c // // Tested on: // * Asus Nexus 7 2013: Qualcomm Snapdragon 600, 32-bit Android 6.0.1 // (Marshmallow). Brings cores online and offline dynamically. (The tablet // has 4 cores. "0", "0-1", "0-2", and "0-3" have all been observed in the // /sys/devices/system/cpu/online file.) This causes the number of cores // currently online to potentially be lower than the number of cores that can // be brought online quickly. // * General Mobile 4G: Qualcomm Snapdragon 410, 32-bit Android 7.1.1 (Nougat). // * Motorola Moto G5 Plus: Qualcomm Snapdragon 625, 32-bit Android 8.1.0 // (Oreo). // * Motorola Moto G7 Play: Qualcomm Snapdragon 632, 32-bit Android 9 (Pie). // All 8 cores have the same cpuinfo_max_freq (1804800), but there are two // values of cpuinfo_min_freq: cores 0-3 have 614400 and cores 4-7 have // 633600. We would need to check cpuinfo_min_freq to differentiate the two // kinds of cores (Qualcomm Kryo 250 Gold and Qualcomm Kryo 250 Silver). // * Pixel 2 XL: Qualcomm Snapdragon 835, 64-bit Android 9 (Pie). // * Pixel 3: Qualcomm Snapdragon 845, 64-bit Android 9 (Pie). // * Pixel 3a: Qualcomm Snapdragon 670, 64-bit Android 9 (Pie). // * Samsung Galaxy S6: Samsung Exynos 7 Octa (7420), 64-bit Android 7.0 // (Nougat). // * Samsung Galaxy S8+ (SM-G955FD): Samsung Exynos 8895, 64-bit Android 8.0.0. // // Note: The sample code needs to use the 'long' type because it is the return // type of the Standard C Library function strtol(). The ClangTidy warnings are // suppressed with NOLINT(google-runtime-int) comments. // Returns the number of online processor cores. int GetNumberOfProcessorsOnline() { // See https://developer.android.com/ndk/guides/cpu-features. long num_cpus = sysconf(_SC_NPROCESSORS_ONLN); // NOLINT(google-runtime-int) if (num_cpus < 0) { LIBGAV1_DLOG(ERROR, "sysconf(_SC_NPROCESSORS_ONLN) failed: %s.", strerror(errno)); return 0; } // It is safe to cast num_cpus to int. sysconf(_SC_NPROCESSORS_ONLN) returns // the return value of get_nprocs(), which is an int. return static_cast(num_cpus); } // These CPUs support heterogeneous multiprocessing. #if defined(__arm__) || defined(__aarch64__) // A helper function used by GetNumberOfPerformanceCoresOnline(). // // Returns the cpuinfo_max_freq value (in kHz) of the given CPU. Returns 0 on // failure. long GetCpuinfoMaxFreq(int cpu_index) { // NOLINT(google-runtime-int) char buffer[128]; const int rv = snprintf( buffer, sizeof(buffer), "/sys/devices/system/cpu/cpu%d/cpufreq/cpuinfo_max_freq", cpu_index); if (rv < 0 || rv >= sizeof(buffer)) { LIBGAV1_DLOG(ERROR, "snprintf failed, or |buffer| is too small."); return 0; } FILE* file = fopen(buffer, "r"); if (file == nullptr) { LIBGAV1_DLOG(ERROR, "fopen(\"%s\", \"r\") failed: %s.", buffer, strerror(errno)); return 0; } char* const str = fgets(buffer, sizeof(buffer), file); fclose(file); if (str == nullptr) { LIBGAV1_DLOG(ERROR, "fgets failed."); return 0; } const long freq = strtol(str, nullptr, 10); // NOLINT(google-runtime-int) if (freq <= 0 || freq == LONG_MAX) { LIBGAV1_DLOG(ERROR, "No conversion can be performed, or the converted value is " "invalid: %ld.", freq); return 0; } return freq; } // Returns the number of performance CPU cores that are online. The number of // efficiency CPU cores is subtracted from the total number of CPU cores. Uses // cpuinfo_max_freq to determine whether a CPU is a performance core or an // efficiency core. // // This function is not perfect. For example, the Snapdragon 632 SoC used in // Motorola Moto G7 has performance and efficiency cores with the same // cpuinfo_max_freq but different cpuinfo_min_freq. This function fails to // differentiate the two kinds of cores and reports all the cores as // performance cores. int GetNumberOfPerformanceCoresOnline() { // Get the online CPU list. Some examples of the online CPU list are: // "0-7" // "0" // "0-1,2,3,4-7" char online[512]; FILE* file = fopen("/sys/devices/system/cpu/online", "r"); if (file == nullptr) { LIBGAV1_DLOG(ERROR, "fopen(\"/sys/devices/system/cpu/online\", \"r\") failed: %s.", strerror(errno)); return 0; } char* const str = fgets(online, sizeof(online), file); fclose(file); file = nullptr; if (str == nullptr) { LIBGAV1_DLOG(ERROR, "fgets failed."); return 0; } LIBGAV1_DLOG(INFO, "The online CPU list is %s", online); // Count the number of the slowest CPUs. Some SoCs such as Snapdragon 855 // have performance cores with different max frequencies, so only the slowest // CPUs are efficiency cores. If we count the number of the fastest CPUs, we // will fail to count the second fastest performance cores. long slowest_cpu_freq = LONG_MAX; // NOLINT(google-runtime-int) int num_slowest_cpus = 0; int num_cpus = 0; const char* cp = online; int range_begin = -1; while (true) { char* str_end; const int cpu = static_cast(strtol(cp, &str_end, 10)); if (str_end == cp) { break; } cp = str_end; if (*cp == '-') { range_begin = cpu; } else { if (range_begin == -1) { range_begin = cpu; } num_cpus += cpu - range_begin + 1; for (int i = range_begin; i <= cpu; ++i) { const long freq = GetCpuinfoMaxFreq(i); // NOLINT(google-runtime-int) if (freq <= 0) { return 0; } LIBGAV1_DLOG(INFO, "cpu%d max frequency is %ld kHz.", i, freq); if (freq < slowest_cpu_freq) { slowest_cpu_freq = freq; num_slowest_cpus = 0; } if (freq == slowest_cpu_freq) { ++num_slowest_cpus; } } range_begin = -1; } if (*cp == '\0') { break; } ++cp; } LIBGAV1_DLOG(INFO, "There are %d CPU cores.", num_cpus); LIBGAV1_DLOG(INFO, "%d CPU cores are the slowest, with max frequency %ld kHz.", num_slowest_cpus, slowest_cpu_freq); // If there are faster CPU cores than the slowest CPU cores, exclude the // slowest CPU cores. if (num_slowest_cpus < num_cpus) { num_cpus -= num_slowest_cpus; } return num_cpus; } #else // Assume symmetric multiprocessing. int GetNumberOfPerformanceCoresOnline() { return GetNumberOfProcessorsOnline(); } #endif #endif // defined(__linux__) /* Run this test with logging enabled on an Android device: 64-bit Android: tests/run_android_test.sh --test cpu --enable_asserts 32-bit Android: tests/run_android_test.sh --test cpu --arch arm \ --enable_asserts */ TEST(CpuTest, GetNumberOfPerformanceCoresOnline) { #if defined(__linux__) const int num_cpus = GetNumberOfProcessorsOnline(); ASSERT_NE(num_cpus, 0); LIBGAV1_DLOG(INFO, "There are %d cores online.", num_cpus); const int num_performance_cpus = GetNumberOfPerformanceCoresOnline(); ASSERT_NE(num_performance_cpus, 0); LIBGAV1_DLOG(INFO, "There are %d performance cores online.", num_performance_cpus); #endif // defined(__linux__) } } // namespace } // namespace libgav1