LLVM OpenMP* Runtime Library
kmp_affinity.cpp
1 /*
2  * kmp_affinity.cpp -- affinity management
3  */
4 
5 //===----------------------------------------------------------------------===//
6 //
7 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
8 // See https://llvm.org/LICENSE.txt for license information.
9 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #include "kmp.h"
14 #include "kmp_affinity.h"
15 #include "kmp_i18n.h"
16 #include "kmp_io.h"
17 #include "kmp_str.h"
18 #include "kmp_wrapper_getpid.h"
19 #if KMP_USE_HIER_SCHED
20 #include "kmp_dispatch_hier.h"
21 #endif
22 #if KMP_USE_HWLOC
23 // Copied from hwloc
24 #define HWLOC_GROUP_KIND_INTEL_MODULE 102
25 #define HWLOC_GROUP_KIND_INTEL_TILE 103
26 #define HWLOC_GROUP_KIND_INTEL_DIE 104
27 #define HWLOC_GROUP_KIND_WINDOWS_PROCESSOR_GROUP 220
28 #endif
29 
30 // The machine topology
31 kmp_topology_t *__kmp_topology = nullptr;
32 // KMP_HW_SUBSET environment variable
33 kmp_hw_subset_t *__kmp_hw_subset = nullptr;
34 
35 // Store the real or imagined machine hierarchy here
36 static hierarchy_info machine_hierarchy;
37 
38 void __kmp_cleanup_hierarchy() { machine_hierarchy.fini(); }
39 
40 void __kmp_get_hierarchy(kmp_uint32 nproc, kmp_bstate_t *thr_bar) {
41  kmp_uint32 depth;
42  // The test below is true if affinity is available, but set to "none". Need to
43  // init on first use of hierarchical barrier.
44  if (TCR_1(machine_hierarchy.uninitialized))
45  machine_hierarchy.init(nproc);
46 
47  // Adjust the hierarchy in case num threads exceeds original
48  if (nproc > machine_hierarchy.base_num_threads)
49  machine_hierarchy.resize(nproc);
50 
51  depth = machine_hierarchy.depth;
52  KMP_DEBUG_ASSERT(depth > 0);
53 
54  thr_bar->depth = depth;
55  __kmp_type_convert(machine_hierarchy.numPerLevel[0] - 1,
56  &(thr_bar->base_leaf_kids));
57  thr_bar->skip_per_level = machine_hierarchy.skipPerLevel;
58 }
59 
60 static int nCoresPerPkg, nPackages;
61 static int __kmp_nThreadsPerCore;
62 #ifndef KMP_DFLT_NTH_CORES
63 static int __kmp_ncores;
64 #endif
65 
66 const char *__kmp_hw_get_catalog_string(kmp_hw_t type, bool plural) {
67  switch (type) {
68  case KMP_HW_SOCKET:
69  return ((plural) ? KMP_I18N_STR(Sockets) : KMP_I18N_STR(Socket));
70  case KMP_HW_DIE:
71  return ((plural) ? KMP_I18N_STR(Dice) : KMP_I18N_STR(Die));
72  case KMP_HW_MODULE:
73  return ((plural) ? KMP_I18N_STR(Modules) : KMP_I18N_STR(Module));
74  case KMP_HW_TILE:
75  return ((plural) ? KMP_I18N_STR(Tiles) : KMP_I18N_STR(Tile));
76  case KMP_HW_NUMA:
77  return ((plural) ? KMP_I18N_STR(NumaDomains) : KMP_I18N_STR(NumaDomain));
78  case KMP_HW_L3:
79  return ((plural) ? KMP_I18N_STR(L3Caches) : KMP_I18N_STR(L3Cache));
80  case KMP_HW_L2:
81  return ((plural) ? KMP_I18N_STR(L2Caches) : KMP_I18N_STR(L2Cache));
82  case KMP_HW_L1:
83  return ((plural) ? KMP_I18N_STR(L1Caches) : KMP_I18N_STR(L1Cache));
84  case KMP_HW_LLC:
85  return ((plural) ? KMP_I18N_STR(LLCaches) : KMP_I18N_STR(LLCache));
86  case KMP_HW_CORE:
87  return ((plural) ? KMP_I18N_STR(Cores) : KMP_I18N_STR(Core));
88  case KMP_HW_THREAD:
89  return ((plural) ? KMP_I18N_STR(Threads) : KMP_I18N_STR(Thread));
90  case KMP_HW_PROC_GROUP:
91  return ((plural) ? KMP_I18N_STR(ProcGroups) : KMP_I18N_STR(ProcGroup));
92  }
93  return KMP_I18N_STR(Unknown);
94 }
95 
96 const char *__kmp_hw_get_keyword(kmp_hw_t type, bool plural) {
97  switch (type) {
98  case KMP_HW_SOCKET:
99  return ((plural) ? "sockets" : "socket");
100  case KMP_HW_DIE:
101  return ((plural) ? "dice" : "die");
102  case KMP_HW_MODULE:
103  return ((plural) ? "modules" : "module");
104  case KMP_HW_TILE:
105  return ((plural) ? "tiles" : "tile");
106  case KMP_HW_NUMA:
107  return ((plural) ? "numa_domains" : "numa_domain");
108  case KMP_HW_L3:
109  return ((plural) ? "l3_caches" : "l3_cache");
110  case KMP_HW_L2:
111  return ((plural) ? "l2_caches" : "l2_cache");
112  case KMP_HW_L1:
113  return ((plural) ? "l1_caches" : "l1_cache");
114  case KMP_HW_LLC:
115  return ((plural) ? "ll_caches" : "ll_cache");
116  case KMP_HW_CORE:
117  return ((plural) ? "cores" : "core");
118  case KMP_HW_THREAD:
119  return ((plural) ? "threads" : "thread");
120  case KMP_HW_PROC_GROUP:
121  return ((plural) ? "proc_groups" : "proc_group");
122  }
123  return ((plural) ? "unknowns" : "unknown");
124 }
125 
127 // kmp_hw_thread_t methods
128 int kmp_hw_thread_t::compare_ids(const void *a, const void *b) {
129  const kmp_hw_thread_t *ahwthread = (const kmp_hw_thread_t *)a;
130  const kmp_hw_thread_t *bhwthread = (const kmp_hw_thread_t *)b;
131  int depth = __kmp_topology->get_depth();
132  for (int level = 0; level < depth; ++level) {
133  if (ahwthread->ids[level] < bhwthread->ids[level])
134  return -1;
135  else if (ahwthread->ids[level] > bhwthread->ids[level])
136  return 1;
137  }
138  if (ahwthread->os_id < bhwthread->os_id)
139  return -1;
140  else if (ahwthread->os_id > bhwthread->os_id)
141  return 1;
142  return 0;
143 }
144 
145 #if KMP_AFFINITY_SUPPORTED
146 int kmp_hw_thread_t::compare_compact(const void *a, const void *b) {
147  int i;
148  const kmp_hw_thread_t *aa = (const kmp_hw_thread_t *)a;
149  const kmp_hw_thread_t *bb = (const kmp_hw_thread_t *)b;
150  int depth = __kmp_topology->get_depth();
151  KMP_DEBUG_ASSERT(__kmp_affinity_compact >= 0);
152  KMP_DEBUG_ASSERT(__kmp_affinity_compact <= depth);
153  for (i = 0; i < __kmp_affinity_compact; i++) {
154  int j = depth - i - 1;
155  if (aa->sub_ids[j] < bb->sub_ids[j])
156  return -1;
157  if (aa->sub_ids[j] > bb->sub_ids[j])
158  return 1;
159  }
160  for (; i < depth; i++) {
161  int j = i - __kmp_affinity_compact;
162  if (aa->sub_ids[j] < bb->sub_ids[j])
163  return -1;
164  if (aa->sub_ids[j] > bb->sub_ids[j])
165  return 1;
166  }
167  return 0;
168 }
169 #endif
170 
171 void kmp_hw_thread_t::print() const {
172  int depth = __kmp_topology->get_depth();
173  printf("%4d ", os_id);
174  for (int i = 0; i < depth; ++i) {
175  printf("%4d ", ids[i]);
176  }
177  printf("\n");
178 }
179 
181 // kmp_topology_t methods
182 
183 // Remove layers that don't add information to the topology.
184 // This is done by having the layer take on the id = UNKNOWN_ID (-1)
185 void kmp_topology_t::_remove_radix1_layers() {
186  int preference[KMP_HW_LAST];
187  int top_index1, top_index2;
188  // Set up preference associative array
189  preference[KMP_HW_PROC_GROUP] = 110;
190  preference[KMP_HW_SOCKET] = 100;
191  preference[KMP_HW_CORE] = 95;
192  preference[KMP_HW_THREAD] = 90;
193  preference[KMP_HW_NUMA] = 85;
194  preference[KMP_HW_DIE] = 80;
195  preference[KMP_HW_TILE] = 75;
196  preference[KMP_HW_MODULE] = 73;
197  preference[KMP_HW_L3] = 70;
198  preference[KMP_HW_L2] = 65;
199  preference[KMP_HW_L1] = 60;
200  preference[KMP_HW_LLC] = 5;
201  top_index1 = 0;
202  top_index2 = 1;
203  while (top_index1 < depth - 1 && top_index2 < depth) {
204  kmp_hw_t type1 = types[top_index1];
205  kmp_hw_t type2 = types[top_index2];
206  KMP_ASSERT_VALID_HW_TYPE(type1);
207  KMP_ASSERT_VALID_HW_TYPE(type2);
208  // Do not allow the three main topology levels (sockets, cores, threads) to
209  // be compacted down
210  if ((type1 == KMP_HW_THREAD || type1 == KMP_HW_CORE ||
211  type1 == KMP_HW_SOCKET) &&
212  (type2 == KMP_HW_THREAD || type2 == KMP_HW_CORE ||
213  type2 == KMP_HW_SOCKET)) {
214  top_index1 = top_index2++;
215  continue;
216  }
217  bool radix1 = true;
218  bool all_same = true;
219  int id1 = hw_threads[0].ids[top_index1];
220  int id2 = hw_threads[0].ids[top_index2];
221  int pref1 = preference[type1];
222  int pref2 = preference[type2];
223  for (int hwidx = 1; hwidx < num_hw_threads; ++hwidx) {
224  if (hw_threads[hwidx].ids[top_index1] == id1 &&
225  hw_threads[hwidx].ids[top_index2] != id2) {
226  radix1 = false;
227  break;
228  }
229  if (hw_threads[hwidx].ids[top_index2] != id2)
230  all_same = false;
231  id1 = hw_threads[hwidx].ids[top_index1];
232  id2 = hw_threads[hwidx].ids[top_index2];
233  }
234  if (radix1) {
235  // Select the layer to remove based on preference
236  kmp_hw_t remove_type, keep_type;
237  int remove_layer, remove_layer_ids;
238  if (pref1 > pref2) {
239  remove_type = type2;
240  remove_layer = remove_layer_ids = top_index2;
241  keep_type = type1;
242  } else {
243  remove_type = type1;
244  remove_layer = remove_layer_ids = top_index1;
245  keep_type = type2;
246  }
247  // If all the indexes for the second (deeper) layer are the same.
248  // e.g., all are zero, then make sure to keep the first layer's ids
249  if (all_same)
250  remove_layer_ids = top_index2;
251  // Remove radix one type by setting the equivalence, removing the id from
252  // the hw threads and removing the layer from types and depth
253  set_equivalent_type(remove_type, keep_type);
254  for (int idx = 0; idx < num_hw_threads; ++idx) {
255  kmp_hw_thread_t &hw_thread = hw_threads[idx];
256  for (int d = remove_layer_ids; d < depth - 1; ++d)
257  hw_thread.ids[d] = hw_thread.ids[d + 1];
258  }
259  for (int idx = remove_layer; idx < depth - 1; ++idx)
260  types[idx] = types[idx + 1];
261  depth--;
262  } else {
263  top_index1 = top_index2++;
264  }
265  }
266  KMP_ASSERT(depth > 0);
267 }
268 
269 void kmp_topology_t::_set_last_level_cache() {
270  if (get_equivalent_type(KMP_HW_L3) != KMP_HW_UNKNOWN)
271  set_equivalent_type(KMP_HW_LLC, KMP_HW_L3);
272  else if (get_equivalent_type(KMP_HW_L2) != KMP_HW_UNKNOWN)
273  set_equivalent_type(KMP_HW_LLC, KMP_HW_L2);
274 #if KMP_MIC_SUPPORTED
275  else if (__kmp_mic_type == mic3) {
276  if (get_equivalent_type(KMP_HW_L2) != KMP_HW_UNKNOWN)
277  set_equivalent_type(KMP_HW_LLC, KMP_HW_L2);
278  else if (get_equivalent_type(KMP_HW_TILE) != KMP_HW_UNKNOWN)
279  set_equivalent_type(KMP_HW_LLC, KMP_HW_TILE);
280  // L2/Tile wasn't detected so just say L1
281  else
282  set_equivalent_type(KMP_HW_LLC, KMP_HW_L1);
283  }
284 #endif
285  else if (get_equivalent_type(KMP_HW_L1) != KMP_HW_UNKNOWN)
286  set_equivalent_type(KMP_HW_LLC, KMP_HW_L1);
287  // Fallback is to set last level cache to socket or core
288  if (get_equivalent_type(KMP_HW_LLC) == KMP_HW_UNKNOWN) {
289  if (get_equivalent_type(KMP_HW_SOCKET) != KMP_HW_UNKNOWN)
290  set_equivalent_type(KMP_HW_LLC, KMP_HW_SOCKET);
291  else if (get_equivalent_type(KMP_HW_CORE) != KMP_HW_UNKNOWN)
292  set_equivalent_type(KMP_HW_LLC, KMP_HW_CORE);
293  }
294  KMP_ASSERT(get_equivalent_type(KMP_HW_LLC) != KMP_HW_UNKNOWN);
295 }
296 
297 // Gather the count of each topology layer and the ratio
298 void kmp_topology_t::_gather_enumeration_information() {
299  int previous_id[KMP_HW_LAST];
300  int max[KMP_HW_LAST];
301 
302  for (int i = 0; i < depth; ++i) {
303  previous_id[i] = kmp_hw_thread_t::UNKNOWN_ID;
304  max[i] = 0;
305  count[i] = 0;
306  ratio[i] = 0;
307  }
308  for (int i = 0; i < num_hw_threads; ++i) {
309  kmp_hw_thread_t &hw_thread = hw_threads[i];
310  for (int layer = 0; layer < depth; ++layer) {
311  int id = hw_thread.ids[layer];
312  if (id != previous_id[layer]) {
313  // Add an additional increment to each count
314  for (int l = layer; l < depth; ++l)
315  count[l]++;
316  // Keep track of topology layer ratio statistics
317  max[layer]++;
318  for (int l = layer + 1; l < depth; ++l) {
319  if (max[l] > ratio[l])
320  ratio[l] = max[l];
321  max[l] = 1;
322  }
323  break;
324  }
325  }
326  for (int layer = 0; layer < depth; ++layer) {
327  previous_id[layer] = hw_thread.ids[layer];
328  }
329  }
330  for (int layer = 0; layer < depth; ++layer) {
331  if (max[layer] > ratio[layer])
332  ratio[layer] = max[layer];
333  }
334 }
335 
336 // Find out if the topology is uniform
337 void kmp_topology_t::_discover_uniformity() {
338  int num = 1;
339  for (int level = 0; level < depth; ++level)
340  num *= ratio[level];
341  flags.uniform = (num == count[depth - 1]);
342 }
343 
344 // Set all the sub_ids for each hardware thread
345 void kmp_topology_t::_set_sub_ids() {
346  int previous_id[KMP_HW_LAST];
347  int sub_id[KMP_HW_LAST];
348 
349  for (int i = 0; i < depth; ++i) {
350  previous_id[i] = -1;
351  sub_id[i] = -1;
352  }
353  for (int i = 0; i < num_hw_threads; ++i) {
354  kmp_hw_thread_t &hw_thread = hw_threads[i];
355  // Setup the sub_id
356  for (int j = 0; j < depth; ++j) {
357  if (hw_thread.ids[j] != previous_id[j]) {
358  sub_id[j]++;
359  for (int k = j + 1; k < depth; ++k) {
360  sub_id[k] = 0;
361  }
362  break;
363  }
364  }
365  // Set previous_id
366  for (int j = 0; j < depth; ++j) {
367  previous_id[j] = hw_thread.ids[j];
368  }
369  // Set the sub_ids field
370  for (int j = 0; j < depth; ++j) {
371  hw_thread.sub_ids[j] = sub_id[j];
372  }
373  }
374 }
375 
376 void kmp_topology_t::_set_globals() {
377  // Set nCoresPerPkg, nPackages, __kmp_nThreadsPerCore, __kmp_ncores
378  int core_level, thread_level, package_level;
379  package_level = get_level(KMP_HW_SOCKET);
380 #if KMP_GROUP_AFFINITY
381  if (package_level == -1)
382  package_level = get_level(KMP_HW_PROC_GROUP);
383 #endif
384  core_level = get_level(KMP_HW_CORE);
385  thread_level = get_level(KMP_HW_THREAD);
386 
387  KMP_ASSERT(core_level != -1);
388  KMP_ASSERT(thread_level != -1);
389 
390  __kmp_nThreadsPerCore = calculate_ratio(thread_level, core_level);
391  if (package_level != -1) {
392  nCoresPerPkg = calculate_ratio(core_level, package_level);
393  nPackages = get_count(package_level);
394  } else {
395  // assume one socket
396  nCoresPerPkg = get_count(core_level);
397  nPackages = 1;
398  }
399 #ifndef KMP_DFLT_NTH_CORES
400  __kmp_ncores = get_count(core_level);
401 #endif
402 }
403 
404 kmp_topology_t *kmp_topology_t::allocate(int nproc, int ndepth,
405  const kmp_hw_t *types) {
406  kmp_topology_t *retval;
407  // Allocate all data in one large allocation
408  size_t size = sizeof(kmp_topology_t) + sizeof(kmp_hw_thread_t) * nproc +
409  sizeof(int) * ndepth * 3;
410  char *bytes = (char *)__kmp_allocate(size);
411  retval = (kmp_topology_t *)bytes;
412  if (nproc > 0) {
413  retval->hw_threads = (kmp_hw_thread_t *)(bytes + sizeof(kmp_topology_t));
414  } else {
415  retval->hw_threads = nullptr;
416  }
417  retval->num_hw_threads = nproc;
418  retval->depth = ndepth;
419  int *arr =
420  (int *)(bytes + sizeof(kmp_topology_t) + sizeof(kmp_hw_thread_t) * nproc);
421  retval->types = (kmp_hw_t *)arr;
422  retval->ratio = arr + ndepth;
423  retval->count = arr + 2 * ndepth;
424  KMP_FOREACH_HW_TYPE(type) { retval->equivalent[type] = KMP_HW_UNKNOWN; }
425  for (int i = 0; i < ndepth; ++i) {
426  retval->types[i] = types[i];
427  retval->equivalent[types[i]] = types[i];
428  }
429  return retval;
430 }
431 
432 void kmp_topology_t::deallocate(kmp_topology_t *topology) {
433  if (topology)
434  __kmp_free(topology);
435 }
436 
437 bool kmp_topology_t::check_ids() const {
438  // Assume ids have been sorted
439  if (num_hw_threads == 0)
440  return true;
441  for (int i = 1; i < num_hw_threads; ++i) {
442  kmp_hw_thread_t &current_thread = hw_threads[i];
443  kmp_hw_thread_t &previous_thread = hw_threads[i - 1];
444  bool unique = false;
445  for (int j = 0; j < depth; ++j) {
446  if (previous_thread.ids[j] != current_thread.ids[j]) {
447  unique = true;
448  break;
449  }
450  }
451  if (unique)
452  continue;
453  return false;
454  }
455  return true;
456 }
457 
458 void kmp_topology_t::dump() const {
459  printf("***********************\n");
460  printf("*** __kmp_topology: ***\n");
461  printf("***********************\n");
462  printf("* depth: %d\n", depth);
463 
464  printf("* types: ");
465  for (int i = 0; i < depth; ++i)
466  printf("%15s ", __kmp_hw_get_keyword(types[i]));
467  printf("\n");
468 
469  printf("* ratio: ");
470  for (int i = 0; i < depth; ++i) {
471  printf("%15d ", ratio[i]);
472  }
473  printf("\n");
474 
475  printf("* count: ");
476  for (int i = 0; i < depth; ++i) {
477  printf("%15d ", count[i]);
478  }
479  printf("\n");
480 
481  printf("* equivalent map:\n");
482  KMP_FOREACH_HW_TYPE(i) {
483  const char *key = __kmp_hw_get_keyword(i);
484  const char *value = __kmp_hw_get_keyword(equivalent[i]);
485  printf("%-15s -> %-15s\n", key, value);
486  }
487 
488  printf("* uniform: %s\n", (is_uniform() ? "Yes" : "No"));
489 
490  printf("* num_hw_threads: %d\n", num_hw_threads);
491  printf("* hw_threads:\n");
492  for (int i = 0; i < num_hw_threads; ++i) {
493  hw_threads[i].print();
494  }
495  printf("***********************\n");
496 }
497 
498 void kmp_topology_t::print(const char *env_var) const {
499  kmp_str_buf_t buf;
500  int print_types_depth;
501  __kmp_str_buf_init(&buf);
502  kmp_hw_t print_types[KMP_HW_LAST + 2];
503 
504  // Num Available Threads
505  KMP_INFORM(AvailableOSProc, env_var, num_hw_threads);
506 
507  // Uniform or not
508  if (is_uniform()) {
509  KMP_INFORM(Uniform, env_var);
510  } else {
511  KMP_INFORM(NonUniform, env_var);
512  }
513 
514  // Equivalent types
515  KMP_FOREACH_HW_TYPE(type) {
516  kmp_hw_t eq_type = equivalent[type];
517  if (eq_type != KMP_HW_UNKNOWN && eq_type != type) {
518  KMP_INFORM(AffEqualTopologyTypes, env_var,
519  __kmp_hw_get_catalog_string(type),
520  __kmp_hw_get_catalog_string(eq_type));
521  }
522  }
523 
524  // Quick topology
525  KMP_ASSERT(depth > 0 && depth <= (int)KMP_HW_LAST);
526  // Create a print types array that always guarantees printing
527  // the core and thread level
528  print_types_depth = 0;
529  for (int level = 0; level < depth; ++level)
530  print_types[print_types_depth++] = types[level];
531  if (equivalent[KMP_HW_CORE] != KMP_HW_CORE) {
532  // Force in the core level for quick topology
533  if (print_types[print_types_depth - 1] == KMP_HW_THREAD) {
534  // Force core before thread e.g., 1 socket X 2 threads/socket
535  // becomes 1 socket X 1 core/socket X 2 threads/socket
536  print_types[print_types_depth - 1] = KMP_HW_CORE;
537  print_types[print_types_depth++] = KMP_HW_THREAD;
538  } else {
539  print_types[print_types_depth++] = KMP_HW_CORE;
540  }
541  }
542  // Always put threads at very end of quick topology
543  if (equivalent[KMP_HW_THREAD] != KMP_HW_THREAD)
544  print_types[print_types_depth++] = KMP_HW_THREAD;
545 
546  __kmp_str_buf_clear(&buf);
547  kmp_hw_t numerator_type;
548  kmp_hw_t denominator_type = KMP_HW_UNKNOWN;
549  int core_level = get_level(KMP_HW_CORE);
550  int ncores = get_count(core_level);
551 
552  for (int plevel = 0, level = 0; plevel < print_types_depth; ++plevel) {
553  int c;
554  bool plural;
555  numerator_type = print_types[plevel];
556  KMP_ASSERT_VALID_HW_TYPE(numerator_type);
557  if (equivalent[numerator_type] != numerator_type)
558  c = 1;
559  else
560  c = get_ratio(level++);
561  plural = (c > 1);
562  if (plevel == 0) {
563  __kmp_str_buf_print(&buf, "%d %s", c,
564  __kmp_hw_get_catalog_string(numerator_type, plural));
565  } else {
566  __kmp_str_buf_print(&buf, " x %d %s/%s", c,
567  __kmp_hw_get_catalog_string(numerator_type, plural),
568  __kmp_hw_get_catalog_string(denominator_type));
569  }
570  denominator_type = numerator_type;
571  }
572  KMP_INFORM(TopologyGeneric, env_var, buf.str, ncores);
573 
574  if (num_hw_threads <= 0) {
575  __kmp_str_buf_free(&buf);
576  return;
577  }
578 
579  // Full OS proc to hardware thread map
580  KMP_INFORM(OSProcToPhysicalThreadMap, env_var);
581  for (int i = 0; i < num_hw_threads; i++) {
582  __kmp_str_buf_clear(&buf);
583  for (int level = 0; level < depth; ++level) {
584  kmp_hw_t type = types[level];
585  __kmp_str_buf_print(&buf, "%s ", __kmp_hw_get_catalog_string(type));
586  __kmp_str_buf_print(&buf, "%d ", hw_threads[i].ids[level]);
<