Compute Library
 21.11
CpuInfo.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2021 Arm Limited.
3  *
4  * SPDX-License-Identifier: MIT
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to
8  * deal in the Software without restriction, including without limitation the
9  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10  * sell copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in all
14  * copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22  * SOFTWARE.
23  */
25 
26 #include "arm_compute/core/Error.h"
27 #include "arm_compute/core/Log.h"
28 #include "support/StringSupport.h"
30 
31 #include <sstream>
32 
33 #if !defined(BARE_METAL)
34 #include <algorithm>
35 #include <cstring>
36 #include <fstream>
37 #include <regex.h> /* C++ std::regex takes up a lot of space in the standalone builds */
38 #include <sched.h>
39 #include <thread>
40 #include <unordered_map>
41 #endif /* !defined(BARE_METAL) */
42 
43 #if !defined(BARE_METAL) && !defined(__APPLE__) && (defined(__arm__) || defined(__aarch64__))
44 #include <asm/hwcap.h> /* Get HWCAP bits from asm/hwcap.h */
45 #include <sys/auxv.h>
46 #endif /* !defined(BARE_METAL) && !defined(__APPLE__) && (defined(__arm__) || defined(__aarch64__)) */
47 
48 #define ARM_COMPUTE_CPU_FEATURE_HWCAP_CPUID (1 << 11)
49 #define ARM_COMPUTE_GET_FEATURE_REG(var, freg) __asm __volatile("MRS %0, " #freg \
50  : "=r"(var))
51 namespace arm_compute
52 {
53 namespace cpuinfo
54 {
55 namespace
56 {
57 #if !defined(BARE_METAL) && !defined(__APPLE__) && (defined(__arm__) || defined(__aarch64__))
58 /** Extract MIDR using CPUID information that are exposed to user-space
59  *
60  * @param[in] max_num_cpus Maximum number of possible CPUs
61  *
62  * @return std::vector<uint32_t> A list of the MIDR of each core
63  */
64 std::vector<uint32_t> midr_from_cpuid(uint32_t max_num_cpus)
65 {
66  std::vector<uint32_t> cpus;
67  for(unsigned int i = 0; i < max_num_cpus; ++i)
68  {
69  std::stringstream str;
70  str << "/sys/devices/system/cpu/cpu" << i << "/regs/identification/midr_el1";
71  std::ifstream file(str.str(), std::ios::in);
72  if(file.is_open())
73  {
74  std::string line;
75  if(bool(getline(file, line)))
76  {
77  cpus.emplace_back(support::cpp11::stoul(line, nullptr, support::cpp11::NumericBase::BASE_16));
78  }
79  }
80  }
81  return cpus;
82 }
83 
84 /** Extract MIDR by parsing the /proc/cpuinfo meta-data
85  *
86  * @param[in] max_num_cpus Maximum number of possible CPUs
87  *
88  * @return std::vector<uint32_t> A list of the MIDR of each core
89  */
90 std::vector<uint32_t> midr_from_proc_cpuinfo(int max_num_cpus)
91 {
92  std::vector<uint32_t> cpus;
93 
94  regex_t proc_regex;
95  regex_t imp_regex;
96  regex_t var_regex;
97  regex_t part_regex;
98  regex_t rev_regex;
99 
100  memset(&proc_regex, 0, sizeof(regex_t));
101  memset(&imp_regex, 0, sizeof(regex_t));
102  memset(&var_regex, 0, sizeof(regex_t));
103  memset(&part_regex, 0, sizeof(regex_t));
104  memset(&rev_regex, 0, sizeof(regex_t));
105 
106  int ret_status = 0;
107  // If "long-form" cpuinfo is present, parse that to populate models.
108  ret_status |= regcomp(&proc_regex, R"(^processor.*([[:digit:]]+)$)", REG_EXTENDED);
109  ret_status |= regcomp(&imp_regex, R"(^CPU implementer.*0x(..)$)", REG_EXTENDED);
110  ret_status |= regcomp(&var_regex, R"(^CPU variant.*0x(.)$)", REG_EXTENDED);
111  ret_status |= regcomp(&part_regex, R"(^CPU part.*0x(...)$)", REG_EXTENDED);
112  ret_status |= regcomp(&rev_regex, R"(^CPU revision.*([[:digit:]]+)$)", REG_EXTENDED);
113  ARM_COMPUTE_UNUSED(ret_status);
114  ARM_COMPUTE_ERROR_ON_MSG(ret_status != 0, "Regex compilation failed.");
115 
116  std::ifstream file("/proc/cpuinfo", std::ios::in);
117  if(file.is_open())
118  {
119  std::string line;
120  int midr = 0;
121  int curcpu = -1;
122 
123  while(bool(getline(file, line)))
124  {
125  std::array<regmatch_t, 2> match;
126  ret_status = regexec(&proc_regex, line.c_str(), 2, match.data(), 0);
127  if(ret_status == 0)
128  {
129  std::string id = line.substr(match[1].rm_so, (match[1].rm_eo - match[1].rm_so));
130  int newcpu = support::cpp11::stoi(id, nullptr);
131 
132  if(curcpu >= 0 && midr == 0)
133  {
134  // Matched a new CPU ID without any description of the previous one - looks like old format.
135  return {};
136  }
137 
138  if(curcpu >= 0 && curcpu < max_num_cpus)
139  {
140  cpus.emplace_back(midr);
141  }
142  else
143  {
144  ARM_COMPUTE_LOG_INFO_MSG_CORE("Trying to populate a core id with id greater than the expected number of cores!");
145  }
146 
147  midr = 0;
148  curcpu = newcpu;
149 
150  continue;
151  }
152 
153  ret_status = regexec(&imp_regex, line.c_str(), 2, match.data(), 0);
154  if(ret_status == 0)
155  {
156  std::string subexp = line.substr(match[1].rm_so, (match[1].rm_eo - match[1].rm_so));
157  int impv = support::cpp11::stoi(subexp, nullptr, support::cpp11::NumericBase::BASE_16);
158  midr |= (impv << 24);
159 
160  continue;
161  }
162 
163  ret_status = regexec(&var_regex, line.c_str(), 2, match.data(), 0);
164  if(ret_status == 0)
165  {
166  std::string subexp = line.substr(match[1].rm_so, (match[1].rm_eo - match[1].rm_so));
167  int varv = support::cpp11::stoi(subexp, nullptr, support::cpp11::NumericBase::BASE_16);
168  midr |= (varv << 20);
169 
170  continue;
171  }
172 
173  ret_status = regexec(&part_regex, line.c_str(), 2, match.data(), 0);
174  if(ret_status == 0)
175  {
176  std::string subexp = line.substr(match[1].rm_so, (match[1].rm_eo - match[1].rm_so));
177  int partv = support::cpp11::stoi(subexp, nullptr, support::cpp11::NumericBase::BASE_16);
178  midr |= (partv << 4);
179 
180  continue;
181  }
182 
183  ret_status = regexec(&rev_regex, line.c_str(), 2, match.data(), 0);
184  if(ret_status == 0)
185  {
186  std::string subexp = line.substr(match[1].rm_so, (match[1].rm_eo - match[1].rm_so));
187  int regv = support::cpp11::stoi(subexp, nullptr);
188  midr |= (regv);
189  midr |= (0xf << 16);
190 
191  continue;
192  }
193  }
194 
195  if(curcpu >= 0 && curcpu < max_num_cpus)
196  {
197  cpus.emplace_back(midr);
198  }
199  else
200  {
201  ARM_COMPUTE_LOG_INFO_MSG_CORE("Trying to populate a core id with id greater than the expected number of cores!");
202  }
203  }
204 
205  // Free allocated memory
206  regfree(&proc_regex);
207  regfree(&imp_regex);
208  regfree(&var_regex);
209  regfree(&part_regex);
210  regfree(&rev_regex);
211 
212  return cpus;
213 }
214 
215 /** Get the maximim number of CPUs in the system by parsing /sys/devices/system/cpu/present
216  *
217  * @return int Maximum number of CPUs
218  */
219 int get_max_cpus()
220 {
221  int max_cpus = 1;
222  std::ifstream CPUspresent;
223  CPUspresent.open("/sys/devices/system/cpu/present", std::ios::in);
224  bool success = false;
225 
226  if(CPUspresent.is_open())
227  {
228  std::string line;
229 
230  if(bool(getline(CPUspresent, line)))
231  {
232  /* The content of this file is a list of ranges or single values, e.g.
233  * 0-5, or 1-3,5,7 or similar. As we are interested in the
234  * max valid ID, we just need to find the last valid
235  * delimiter ('-' or ',') and parse the integer immediately after that.
236  */
237  auto startfrom = line.begin();
238 
239  for(auto i = line.begin(); i < line.end(); ++i)
240  {
241  if(*i == '-' || *i == ',')
242  {
243  startfrom = i + 1;
244  }
245  }
246 
247  line.erase(line.begin(), startfrom);
248 
249  max_cpus = support::cpp11::stoi(line, nullptr) + 1;
250  success = true;
251  }
252  }
253 
254  // Return std::thread::hardware_concurrency() as a fallback.
255  if(!success)
256  {
257  max_cpus = std::thread::hardware_concurrency();
258  }
259  return max_cpus;
260 }
261 #endif /* !defined(BARE_METAL) && !defined(__APPLE__) && (defined(__arm__) || defined(__aarch64__)) */
262 
263 #if defined(BARE_METAL) && defined(__aarch64__)
264 uint64_t get_sve_feature_reg()
265 {
266  uint64_t svefr0 = 0;
267  __asm __volatile(
268  ".inst 0xd5380483 // mrs x3, ID_AA64ZFR0_EL1\n"
269  "MOV %0, X3"
270  : "=r"(svefr0)
271  :
272  : "x3");
273  return svefr0;
274 }
275 #endif /* defined(BARE_METAL) && defined(__aarch64__) */
276 } // namespace
277 
278 CpuInfo::CpuInfo(CpuIsaInfo isa, std::vector<CpuModel> cpus)
279  : _isa(std::move(isa)), _cpus(std::move(cpus))
280 {
281 }
282 
284 {
285 #if !defined(BARE_METAL) && !defined(__APPLE__) && (defined(__arm__) || defined(__aarch64__))
286  const uint32_t hwcaps = getauxval(AT_HWCAP);
287  const uint32_t hwcaps2 = getauxval(AT_HWCAP2);
288  const uint32_t max_cpus = get_max_cpus();
289 
290  // Populate midr values
291  std::vector<uint32_t> cpus_midr;
293  {
294  cpus_midr = midr_from_cpuid(max_cpus);
295  }
296  if(cpus_midr.empty())
297  {
298  cpus_midr = midr_from_proc_cpuinfo(max_cpus);
299  }
300  if(cpus_midr.empty())
301  {
302  cpus_midr.resize(max_cpus, 0);
303  }
304 
305  // Populate isa (Assume homogeneous ISA specification)
306  CpuIsaInfo isa = init_cpu_isa_from_hwcaps(hwcaps, hwcaps2, cpus_midr.back());
307 
308  // Convert midr to models
309  std::vector<CpuModel> cpus_model;
310  std::transform(std::begin(cpus_midr), std::end(cpus_midr), std::back_inserter(cpus_model),
311  [](uint32_t midr) -> CpuModel { return midr_to_model(midr); });
312 
313  CpuInfo info(isa, cpus_model);
314  return info;
315 
316 #elif(BARE_METAL) && defined(__aarch64__) /* !defined(BARE_METAL) && !defined(__APPLE__) && (defined(__arm__) || defined(__aarch64__)) */
317 
318  // Assume single CPU in bare metal mode. Just read the ID register and feature bits directly.
319  uint64_t isar0 = 0, isar1 = 0, pfr0 = 0, svefr0 = 0, midr = 0;
320  ARM_COMPUTE_GET_FEATURE_REG(isar0, ID_AA64ISAR0_EL1);
321  ARM_COMPUTE_GET_FEATURE_REG(isar1, ID_AA64ISAR1_EL1);
322  ARM_COMPUTE_GET_FEATURE_REG(pfr0, ID_AA64PFR0_EL1);
323  ARM_COMPUTE_GET_FEATURE_REG(midr, MIDR_EL1);
324  if((pfr0 >> 32) & 0xf)
325  {
326  svefr0 = get_sve_feature_reg();
327  }
328 
329  CpuIsaInfo isa = init_cpu_isa_from_regs(isar0, isar1, pfr0, svefr0, midr);
330  std::vector<CpuModel> cpus_model(1, midr_to_model(midr));
331  CpuInfo info(isa, cpus_model);
332  return info;
333 #else /* #elif(BARE_METAL) && defined(__aarch64__) */
335  return info;
336 #endif /* !defined(BARE_METAL) && !defined(__APPLE__) && (defined(__arm__) || defined(__aarch64__)) */
337 }
338 
339 CpuModel CpuInfo::cpu_model(uint32_t cpuid) const
340 {
341  if(cpuid < _cpus.size())
342  {
343  return _cpus[cpuid];
344  }
345  return CpuModel::GENERIC;
346 }
347 
349 {
350 #if defined(BARE_METAL) || defined(__APPLE__) || (!defined(__arm__) && !defined(__aarch64__))
351  return cpu_model(0);
352 #else /* defined(BARE_METAL) || defined(__APPLE__) || (!defined(__arm__) && !defined(__aarch64__)) */
353  return cpu_model(sched_getcpu());
354 #endif /* defined(BARE_METAL) || defined(__APPLE__) || (!defined(__arm__) && !defined(__aarch64__)) */
355 }
356 
357 uint32_t CpuInfo::num_cpus() const
358 {
359  return _cpus.size();
360 }
361 
363 {
364  unsigned int num_threads_hint = 1;
365 
366 #if !defined(BARE_METAL)
367  std::vector<std::string> cpus;
368  cpus.reserve(64);
369 
370  // CPU part regex
371  regex_t cpu_part_rgx;
372  memset(&cpu_part_rgx, 0, sizeof(regex_t));
373  int ret_status = regcomp(&cpu_part_rgx, R"(.*CPU part.+/?\:[[:space:]]+([[:alnum:]]+).*)", REG_EXTENDED);
374  ARM_COMPUTE_UNUSED(ret_status);
375  ARM_COMPUTE_ERROR_ON_MSG(ret_status != 0, "Regex compilation failed.");
376 
377  // Read cpuinfo and get occurrence of each core
378  std::ifstream cpuinfo_file("/proc/cpuinfo", std::ios::in);
379  if(cpuinfo_file.is_open())
380  {
381  std::string line;
382  while(bool(getline(cpuinfo_file, line)))
383  {
384  std::array<regmatch_t, 2> match;
385  if(regexec(&cpu_part_rgx, line.c_str(), 2, match.data(), 0) == 0)
386  {
387  cpus.emplace_back(line.substr(match[1].rm_so, (match[1].rm_eo - match[1].rm_so)));
388  }
389  }
390  }
391  regfree(&cpu_part_rgx);
392 
393  // Get min number of threads
394  std::sort(std::begin(cpus), std::end(cpus));
395  auto least_frequent_cpu_occurences = [](const std::vector<std::string> &cpus) -> uint32_t
396  {
397  std::unordered_map<std::string, uint32_t> cpus_freq;
398  for(const auto &cpu : cpus)
399  {
400  cpus_freq[cpu]++;
401  }
402 
403  uint32_t vmin = cpus.size() + 1;
404  for(const auto &cpu_freq : cpus_freq)
405  {
406  vmin = std::min(vmin, cpu_freq.second);
407  }
408  return vmin;
409  };
410 
411  // Set thread hint
412  num_threads_hint = cpus.empty() ? std::thread::hardware_concurrency() : least_frequent_cpu_occurences(cpus);
413 #endif /* !defined(BARE_METAL) */
414 
415  return num_threads_hint;
416 }
417 } // namespace cpuinfo
418 } // namespace arm_compute
unsigned long stoul(const std::string &str, std::size_t *pos=0, NumericBase base=NumericBase::BASE_10)
Convert string values to unsigned long.
Definition: StringSupport.h:91
static CpuInfo build()
CpuInfo builder function from system related information.
Definition: CpuInfo.cpp:283
const std::vector< CpuModel > & cpus() const
Definition: CpuInfo.h:107
#define ARM_COMPUTE_GET_FEATURE_REG(var, freg)
Definition: CpuInfo.cpp:49
CPUModel
CPU models types.
Definition: CPPTypes.h:52
uint32_t num_threads_hint()
Some systems have both big and small cores, this fuction computes the minimum number of cores that ar...
Definition: CpuInfo.cpp:362
Copyright (c) 2017-2021 Arm Limited.
CPU ISA (Instruction Set Architecture) information.
Definition: CpuIsaInfo.h:37
Aggregate class that contains CPU related information.
Definition: CpuInfo.h:44
const CpuIsaInfo & isa() const
Definition: CpuInfo.h:103
#define ARM_COMPUTE_UNUSED(...)
To avoid unused variables warnings.
Definition: Error.h:152
#define ARM_COMPUTE_ERROR_ON_MSG(cond, msg)
Definition: Error.h:456
uint8x8_t vmin(const uint8x8_t &a, const uint8x8_t &b)
Definition: min.h:39
CpuIsaInfo init_cpu_isa_from_hwcaps(uint32_t hwcaps, uint32_t hwcaps2, uint32_t midr)
Identify ISA related information through system information.
Definition: CpuIsaInfo.cpp:131
void end(TokenStream &in, bool &valid)
Definition: MLGOParser.cpp:290
int stoi(const std::string &str, std::size_t *pos=0, NumericBase base=NumericBase::BASE_10)
Convert string values to integer.
Definition: StringSupport.h:55
uint32_t num_cpus() const
Definition: CpuInfo.cpp:357
CpuModel cpu_model() const
Definition: CpuInfo.cpp:348
ScaleKernelInfo info(interpolation_policy, default_border_mode, PixelValue(), sampling_policy, false)
CpuIsaInfo init_cpu_isa_from_regs(uint64_t isar0, uint64_t isar1, uint64_t pfr0, uint64_t svefr0, uint64_t midr)
Identify ISA related information through register information.
Definition: CpuIsaInfo.cpp:143
#define ARM_COMPUTE_LOG_INFO_MSG_CORE(msg)
Log information level message to the core system logger.
Definition: Log.h:87
CpuInfo()=default
Default constructor.
#define ARM_COMPUTE_CPU_FEATURE_HWCAP_CPUID
Definition: CpuInfo.cpp:48
CpuModel midr_to_model(uint32_t midr)
Extract the model type from the MIDR value.
Definition: CpuModel.cpp:78