xref: /oneTBB/src/tbbbind/tbb_bind.cpp (revision 49e08aac)
151c0b2f7Stbbdev /*
251c0b2f7Stbbdev     Copyright (c) 2019-2020 Intel Corporation
351c0b2f7Stbbdev 
451c0b2f7Stbbdev     Licensed under the Apache License, Version 2.0 (the "License");
551c0b2f7Stbbdev     you may not use this file except in compliance with the License.
651c0b2f7Stbbdev     You may obtain a copy of the License at
751c0b2f7Stbbdev 
851c0b2f7Stbbdev         http://www.apache.org/licenses/LICENSE-2.0
951c0b2f7Stbbdev 
1051c0b2f7Stbbdev     Unless required by applicable law or agreed to in writing, software
1151c0b2f7Stbbdev     distributed under the License is distributed on an "AS IS" BASIS,
1251c0b2f7Stbbdev     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1351c0b2f7Stbbdev     See the License for the specific language governing permissions and
1451c0b2f7Stbbdev     limitations under the License.
1551c0b2f7Stbbdev */
1651c0b2f7Stbbdev 
1751c0b2f7Stbbdev #include "../tbb/assert_impl.h" // Out-of-line TBB assertion handling routines are instantiated here.
18*49e08aacStbbdev #include "oneapi/tbb/detail/_assert.h"
1951c0b2f7Stbbdev 
2051c0b2f7Stbbdev #if _MSC_VER && !__INTEL_COMPILER
2151c0b2f7Stbbdev #pragma warning( push )
2251c0b2f7Stbbdev #pragma warning( disable : 4100 )
2351c0b2f7Stbbdev #endif
2451c0b2f7Stbbdev #include <hwloc.h>
2551c0b2f7Stbbdev #if _MSC_VER && !__INTEL_COMPILER
2651c0b2f7Stbbdev #pragma warning( pop )
2751c0b2f7Stbbdev #endif
2851c0b2f7Stbbdev 
2951c0b2f7Stbbdev #include <vector>
3051c0b2f7Stbbdev 
3151c0b2f7Stbbdev // Most of hwloc calls returns negative exit code on error.
3251c0b2f7Stbbdev // This macro tracks error codes that are returned from the hwloc interfaces.
3351c0b2f7Stbbdev #define assertion_hwloc_wrapper(command, ...) \
3451c0b2f7Stbbdev         __TBB_ASSERT_EX( (command(__VA_ARGS__)) >= 0, "Error occurred during call to hwloc API.");
3551c0b2f7Stbbdev 
3651c0b2f7Stbbdev namespace tbb {
3751c0b2f7Stbbdev namespace detail {
3851c0b2f7Stbbdev namespace r1 {
3951c0b2f7Stbbdev 
4051c0b2f7Stbbdev //------------------------------------------------------------------------
4151c0b2f7Stbbdev // Information about the machine's hardware TBB is happen to work on
4251c0b2f7Stbbdev //------------------------------------------------------------------------
4351c0b2f7Stbbdev class platform_topology {
4451c0b2f7Stbbdev     friend class numa_affinity_handler;
4551c0b2f7Stbbdev 
4651c0b2f7Stbbdev     // TODO: add the `my_` prefix to the members
4751c0b2f7Stbbdev     hwloc_topology_t topology;
4851c0b2f7Stbbdev     hwloc_cpuset_t   process_cpu_affinity_mask;
4951c0b2f7Stbbdev     hwloc_nodeset_t  process_node_affinity_mask;
5051c0b2f7Stbbdev     std::vector<hwloc_cpuset_t>  affinity_masks_list;
5151c0b2f7Stbbdev 
5251c0b2f7Stbbdev     std::vector<int> default_concurrency_list;
5351c0b2f7Stbbdev     std::vector<int> numa_indexes_list;
5451c0b2f7Stbbdev     int  numa_nodes_count;
5551c0b2f7Stbbdev 
5651c0b2f7Stbbdev     enum init_stages { uninitialized,
5751c0b2f7Stbbdev                        started,
5851c0b2f7Stbbdev                        topology_allocated,
5951c0b2f7Stbbdev                        topology_loaded,
6051c0b2f7Stbbdev                        topology_parsed } initialization_state;
6151c0b2f7Stbbdev 
6251c0b2f7Stbbdev     // Binding threads to NUMA nodes that locates in another Windows Processor groups
6351c0b2f7Stbbdev     // is allowed only if machine topology contains several Windows Processors groups
6451c0b2f7Stbbdev     // and process affinity mask wasn`t limited manually (affinity mask cannot violates
6551c0b2f7Stbbdev     // processors group boundaries).
6651c0b2f7Stbbdev     bool intergroup_binding_allowed(size_t groups_num) { return groups_num > 1; }
6751c0b2f7Stbbdev 
6851c0b2f7Stbbdev     platform_topology() : topology(nullptr),
6951c0b2f7Stbbdev                           process_cpu_affinity_mask(nullptr),
7051c0b2f7Stbbdev                           process_node_affinity_mask(nullptr),
7151c0b2f7Stbbdev                           numa_nodes_count(0),
7251c0b2f7Stbbdev                           initialization_state(uninitialized) {}
7351c0b2f7Stbbdev 
7451c0b2f7Stbbdev public:
7551c0b2f7Stbbdev     typedef hwloc_cpuset_t             affinity_mask;
7651c0b2f7Stbbdev     typedef hwloc_const_cpuset_t const_affinity_mask;
7751c0b2f7Stbbdev 
7851c0b2f7Stbbdev     static platform_topology& instance() {
7951c0b2f7Stbbdev         static platform_topology topology;
8051c0b2f7Stbbdev         return topology;
8151c0b2f7Stbbdev     }
8251c0b2f7Stbbdev 
8351c0b2f7Stbbdev     bool is_topology_parsed() { return initialization_state == topology_parsed; }
8451c0b2f7Stbbdev 
8551c0b2f7Stbbdev     void initialize( size_t groups_num ) {
8651c0b2f7Stbbdev         if ( initialization_state != uninitialized )
8751c0b2f7Stbbdev             return;
8851c0b2f7Stbbdev         initialization_state = started;
8951c0b2f7Stbbdev 
9051c0b2f7Stbbdev         // Parse topology
9151c0b2f7Stbbdev         if ( hwloc_topology_init( &topology ) == 0 ) {
9251c0b2f7Stbbdev             initialization_state = topology_allocated;
9351c0b2f7Stbbdev             if ( hwloc_topology_load( topology ) == 0 ) {
9451c0b2f7Stbbdev                 initialization_state = topology_loaded;
9551c0b2f7Stbbdev             }
9651c0b2f7Stbbdev         }
9751c0b2f7Stbbdev 
9851c0b2f7Stbbdev         // Fill parameters with stubs if topology parsing is broken.
9951c0b2f7Stbbdev         if ( initialization_state != topology_loaded ) {
10051c0b2f7Stbbdev             if ( initialization_state == topology_allocated ) {
10151c0b2f7Stbbdev                 hwloc_topology_destroy(topology);
10251c0b2f7Stbbdev             }
10351c0b2f7Stbbdev             numa_nodes_count = 1;
10451c0b2f7Stbbdev             numa_indexes_list.push_back(-1);
10551c0b2f7Stbbdev             default_concurrency_list.push_back(-1);
10651c0b2f7Stbbdev             return;
10751c0b2f7Stbbdev         }
10851c0b2f7Stbbdev 
10951c0b2f7Stbbdev         // Getting process affinity mask
11051c0b2f7Stbbdev         if ( intergroup_binding_allowed(groups_num) ) {
11151c0b2f7Stbbdev             process_cpu_affinity_mask  = hwloc_bitmap_dup(hwloc_topology_get_complete_cpuset (topology));
11251c0b2f7Stbbdev             process_node_affinity_mask = hwloc_bitmap_dup(hwloc_topology_get_complete_nodeset(topology));
11351c0b2f7Stbbdev         } else {
11451c0b2f7Stbbdev             process_cpu_affinity_mask  = hwloc_bitmap_alloc();
11551c0b2f7Stbbdev             process_node_affinity_mask = hwloc_bitmap_alloc();
11651c0b2f7Stbbdev 
11751c0b2f7Stbbdev             assertion_hwloc_wrapper(hwloc_get_cpubind, topology, process_cpu_affinity_mask, 0);
11851c0b2f7Stbbdev             hwloc_cpuset_to_nodeset(topology, process_cpu_affinity_mask, process_node_affinity_mask);
11951c0b2f7Stbbdev         }
12051c0b2f7Stbbdev 
12151c0b2f7Stbbdev         // Get the number of available NUMA nodes
12251c0b2f7Stbbdev         // If system contains no NUMA nodes, HWLOC 1.11 returns an infinitely filled bitmap.
12351c0b2f7Stbbdev         // hwloc_bitmap_weight() returns negative value for such bitmaps, so we use this check
12451c0b2f7Stbbdev         // to change way of topology initialization.
12551c0b2f7Stbbdev         numa_nodes_count = hwloc_bitmap_weight(process_node_affinity_mask);
12651c0b2f7Stbbdev         if (numa_nodes_count <= 0) {
12751c0b2f7Stbbdev             // numa_nodes_count may be empty if the process affinity mask is empty too (invalid case)
12851c0b2f7Stbbdev             // or if some internal HWLOC error occurred.
12951c0b2f7Stbbdev             // So we place -1 as index in this case.
13051c0b2f7Stbbdev             numa_indexes_list.push_back(numa_nodes_count == 0 ? -1 : 0);
13151c0b2f7Stbbdev             numa_nodes_count = 1;
13251c0b2f7Stbbdev             default_concurrency_list.push_back(hwloc_bitmap_weight(process_cpu_affinity_mask));
13351c0b2f7Stbbdev 
13451c0b2f7Stbbdev             affinity_masks_list.push_back(hwloc_bitmap_dup(process_cpu_affinity_mask));
13551c0b2f7Stbbdev             initialization_state = topology_parsed;
13651c0b2f7Stbbdev             return;
13751c0b2f7Stbbdev         }
13851c0b2f7Stbbdev 
13951c0b2f7Stbbdev         // Get NUMA logical indexes list
14051c0b2f7Stbbdev         unsigned counter = 0;
14151c0b2f7Stbbdev         int i = 0;
14251c0b2f7Stbbdev         int max_numa_index = -1;
14351c0b2f7Stbbdev         numa_indexes_list.resize(numa_nodes_count);
14451c0b2f7Stbbdev         hwloc_obj_t node_buffer;
14551c0b2f7Stbbdev         hwloc_bitmap_foreach_begin(i, process_node_affinity_mask) {
14651c0b2f7Stbbdev             node_buffer = hwloc_get_obj_by_type(topology, HWLOC_OBJ_NUMANODE, i);
14751c0b2f7Stbbdev             numa_indexes_list[counter] = static_cast<int>(node_buffer->logical_index);
14851c0b2f7Stbbdev 
14951c0b2f7Stbbdev             if ( numa_indexes_list[counter] > max_numa_index ) {
15051c0b2f7Stbbdev                 max_numa_index = numa_indexes_list[counter];
15151c0b2f7Stbbdev             }
15251c0b2f7Stbbdev 
15351c0b2f7Stbbdev             counter++;
15451c0b2f7Stbbdev         } hwloc_bitmap_foreach_end();
15551c0b2f7Stbbdev         __TBB_ASSERT(max_numa_index >= 0, "Maximal NUMA index must not be negative");
15651c0b2f7Stbbdev 
15751c0b2f7Stbbdev         // Fill concurrency and affinity masks lists
15851c0b2f7Stbbdev         default_concurrency_list.resize(max_numa_index + 1);
15951c0b2f7Stbbdev         affinity_masks_list.resize(max_numa_index + 1);
16051c0b2f7Stbbdev 
16151c0b2f7Stbbdev         int index = 0;
16251c0b2f7Stbbdev         hwloc_bitmap_foreach_begin(i, process_node_affinity_mask) {
16351c0b2f7Stbbdev             node_buffer = hwloc_get_obj_by_type(topology, HWLOC_OBJ_NUMANODE, i);
16451c0b2f7Stbbdev             index = static_cast<int>(node_buffer->logical_index);
16551c0b2f7Stbbdev 
16651c0b2f7Stbbdev             hwloc_cpuset_t& current_mask = affinity_masks_list[index];
16751c0b2f7Stbbdev             current_mask = hwloc_bitmap_dup(node_buffer->cpuset);
16851c0b2f7Stbbdev 
16951c0b2f7Stbbdev             hwloc_bitmap_and(current_mask, current_mask, process_cpu_affinity_mask);
17051c0b2f7Stbbdev             __TBB_ASSERT(!hwloc_bitmap_iszero(current_mask), "hwloc detected unavailable NUMA node");
17151c0b2f7Stbbdev             default_concurrency_list[index] = hwloc_bitmap_weight(current_mask);
17251c0b2f7Stbbdev         } hwloc_bitmap_foreach_end();
17351c0b2f7Stbbdev         initialization_state = topology_parsed;
17451c0b2f7Stbbdev     }
17551c0b2f7Stbbdev 
17651c0b2f7Stbbdev     ~platform_topology() {
17751c0b2f7Stbbdev         if ( is_topology_parsed() ) {
17851c0b2f7Stbbdev             for (int i = 0; i < numa_nodes_count; i++) {
17951c0b2f7Stbbdev                 hwloc_bitmap_free(affinity_masks_list[numa_indexes_list[i]]);
18051c0b2f7Stbbdev             }
18151c0b2f7Stbbdev             hwloc_bitmap_free(process_node_affinity_mask);
18251c0b2f7Stbbdev             hwloc_bitmap_free(process_cpu_affinity_mask);
18351c0b2f7Stbbdev         }
18451c0b2f7Stbbdev 
18551c0b2f7Stbbdev         if ( initialization_state >= topology_allocated ) {
18651c0b2f7Stbbdev             hwloc_topology_destroy(topology);
18751c0b2f7Stbbdev         }
18851c0b2f7Stbbdev 
18951c0b2f7Stbbdev         initialization_state = uninitialized;
19051c0b2f7Stbbdev     }
19151c0b2f7Stbbdev 
19251c0b2f7Stbbdev     void fill(int& nodes_count, int*& indexes_list, int*& concurrency_list ) {
19351c0b2f7Stbbdev         __TBB_ASSERT(is_topology_parsed(), "Trying to get access to uninitialized platform_topology");
19451c0b2f7Stbbdev         nodes_count = numa_nodes_count;
19551c0b2f7Stbbdev         indexes_list = &numa_indexes_list.front();
19651c0b2f7Stbbdev         concurrency_list = &default_concurrency_list.front();
19751c0b2f7Stbbdev     }
19851c0b2f7Stbbdev 
19951c0b2f7Stbbdev     affinity_mask allocate_process_affinity_mask() {
20051c0b2f7Stbbdev         __TBB_ASSERT(is_topology_parsed(), "Trying to get access to uninitialized platform_topology");
20151c0b2f7Stbbdev         return hwloc_bitmap_dup(process_cpu_affinity_mask);
20251c0b2f7Stbbdev     }
20351c0b2f7Stbbdev 
20451c0b2f7Stbbdev     void free_affinity_mask( affinity_mask mask_to_free ) {
20551c0b2f7Stbbdev         hwloc_bitmap_free(mask_to_free); // If bitmap is nullptr, no operation is performed.
20651c0b2f7Stbbdev     }
20751c0b2f7Stbbdev 
20851c0b2f7Stbbdev     void store_current_affinity_mask( affinity_mask current_mask ) {
20951c0b2f7Stbbdev         assertion_hwloc_wrapper(hwloc_get_cpubind, topology, current_mask, HWLOC_CPUBIND_THREAD);
21051c0b2f7Stbbdev 
21151c0b2f7Stbbdev         hwloc_bitmap_and(current_mask, current_mask, process_cpu_affinity_mask);
21251c0b2f7Stbbdev         __TBB_ASSERT(!hwloc_bitmap_iszero(current_mask),
21351c0b2f7Stbbdev             "Current affinity mask must intersects with process affinity mask");
21451c0b2f7Stbbdev     }
21551c0b2f7Stbbdev 
21651c0b2f7Stbbdev     void set_new_affinity_mask( const_affinity_mask new_mask ) {
21751c0b2f7Stbbdev         assertion_hwloc_wrapper(hwloc_set_cpubind, topology, new_mask, HWLOC_CPUBIND_THREAD);
21851c0b2f7Stbbdev     }
21951c0b2f7Stbbdev 
22051c0b2f7Stbbdev     const_affinity_mask get_node_affinity_mask( int node_index ) {
22151c0b2f7Stbbdev         __TBB_ASSERT((int)affinity_masks_list.size() > node_index,
22251c0b2f7Stbbdev             "Trying to get affinity mask for uninitialized NUMA node");
22351c0b2f7Stbbdev         return affinity_masks_list[node_index];
22451c0b2f7Stbbdev     }
22551c0b2f7Stbbdev };
22651c0b2f7Stbbdev 
22751c0b2f7Stbbdev class binding_handler {
22851c0b2f7Stbbdev     // Following vector saves thread affinity mask on scheduler entry to return it to this thread
22951c0b2f7Stbbdev     // on scheduler exit.
23051c0b2f7Stbbdev     typedef std::vector<platform_topology::affinity_mask> affinity_masks_container;
23151c0b2f7Stbbdev     affinity_masks_container affinity_backup;
23251c0b2f7Stbbdev 
23351c0b2f7Stbbdev public:
23451c0b2f7Stbbdev     binding_handler( size_t size ) : affinity_backup(size) {
23551c0b2f7Stbbdev         for (affinity_masks_container::iterator it = affinity_backup.begin();
23651c0b2f7Stbbdev              it != affinity_backup.end(); it++) {
23751c0b2f7Stbbdev             *it = platform_topology::instance().allocate_process_affinity_mask();
23851c0b2f7Stbbdev         }
23951c0b2f7Stbbdev     }
24051c0b2f7Stbbdev 
24151c0b2f7Stbbdev     ~binding_handler() {
24251c0b2f7Stbbdev         for (affinity_masks_container::iterator it = affinity_backup.begin();
24351c0b2f7Stbbdev              it != affinity_backup.end(); it++) {
24451c0b2f7Stbbdev             platform_topology::instance().free_affinity_mask(*it);
24551c0b2f7Stbbdev         }
24651c0b2f7Stbbdev     }
24751c0b2f7Stbbdev 
24851c0b2f7Stbbdev     void bind_thread_to_node( unsigned slot_num, unsigned numa_node_id ) {
24951c0b2f7Stbbdev         __TBB_ASSERT(slot_num < affinity_backup.size(),
25051c0b2f7Stbbdev             "The slot number is greater than the number of slots in the arena");
25151c0b2f7Stbbdev         __TBB_ASSERT(platform_topology::instance().is_topology_parsed(),
25251c0b2f7Stbbdev             "Trying to get access to uninitialized platform_topology");
25351c0b2f7Stbbdev         platform_topology::instance().store_current_affinity_mask(affinity_backup[slot_num]);
25451c0b2f7Stbbdev 
25551c0b2f7Stbbdev         platform_topology::instance().set_new_affinity_mask(
25651c0b2f7Stbbdev             platform_topology::instance().get_node_affinity_mask(numa_node_id));
25751c0b2f7Stbbdev     }
25851c0b2f7Stbbdev 
25951c0b2f7Stbbdev     void restore_previous_affinity_mask( unsigned slot_num ) {
26051c0b2f7Stbbdev         __TBB_ASSERT(platform_topology::instance().is_topology_parsed(),
26151c0b2f7Stbbdev             "Trying to get access to uninitialized platform_topology");
26251c0b2f7Stbbdev         platform_topology::instance().set_new_affinity_mask(affinity_backup[slot_num]);
26351c0b2f7Stbbdev     };
26451c0b2f7Stbbdev 
26551c0b2f7Stbbdev };
26651c0b2f7Stbbdev 
26751c0b2f7Stbbdev extern "C" { // exported to TBB interfaces
26851c0b2f7Stbbdev 
26951c0b2f7Stbbdev void __TBB_internal_initialize_numa_topology( size_t groups_num, int& nodes_count, int*& indexes_list, int*& concurrency_list ) {
27051c0b2f7Stbbdev     platform_topology::instance().initialize(groups_num);
27151c0b2f7Stbbdev     platform_topology::instance().fill(nodes_count, indexes_list, concurrency_list);
27251c0b2f7Stbbdev }
27351c0b2f7Stbbdev 
27451c0b2f7Stbbdev binding_handler* __TBB_internal_allocate_binding_handler(int slot_num) {
27551c0b2f7Stbbdev     __TBB_ASSERT(slot_num > 0, "Trying to create numa handler for 0 threads.");
27651c0b2f7Stbbdev     return new binding_handler(slot_num);
27751c0b2f7Stbbdev }
27851c0b2f7Stbbdev 
27951c0b2f7Stbbdev void __TBB_internal_deallocate_binding_handler(binding_handler* handler_ptr) {
28051c0b2f7Stbbdev     __TBB_ASSERT(handler_ptr != nullptr, "Trying to deallocate nullptr pointer.");
28151c0b2f7Stbbdev     delete handler_ptr;
28251c0b2f7Stbbdev }
28351c0b2f7Stbbdev 
28451c0b2f7Stbbdev void __TBB_internal_bind_to_node(binding_handler* handler_ptr, int slot_num, int numa_id) {
28551c0b2f7Stbbdev     __TBB_ASSERT(handler_ptr != nullptr, "Trying to get access to uninitialized metadata.");
28651c0b2f7Stbbdev     __TBB_ASSERT(platform_topology::instance().is_topology_parsed(),
28751c0b2f7Stbbdev         "Trying to get access to uninitialized platform_topology.");
28851c0b2f7Stbbdev     handler_ptr->bind_thread_to_node(slot_num, numa_id);
28951c0b2f7Stbbdev }
29051c0b2f7Stbbdev 
29151c0b2f7Stbbdev void __TBB_internal_restore_affinity(binding_handler* handler_ptr, int slot_num) {
29251c0b2f7Stbbdev     __TBB_ASSERT(handler_ptr != nullptr, "Trying to get access to uninitialized metadata.");
29351c0b2f7Stbbdev     __TBB_ASSERT(platform_topology::instance().is_topology_parsed(),
29451c0b2f7Stbbdev         "Trying to get access to uninitialized platform_topology.");
29551c0b2f7Stbbdev     handler_ptr->restore_previous_affinity_mask(slot_num);
29651c0b2f7Stbbdev }
29751c0b2f7Stbbdev 
29851c0b2f7Stbbdev } // extern "C"
29951c0b2f7Stbbdev 
30051c0b2f7Stbbdev } // namespace r1
30151c0b2f7Stbbdev } // namespace detail
30251c0b2f7Stbbdev } // namespace tbb
30351c0b2f7Stbbdev 
30451c0b2f7Stbbdev #undef assertion_hwloc_wrapper
305