Skip to content

Commit 65e799f

Browse files
committed
Add tests for memspace "highest bandwidth"
Those tests are skipped with GTEST_SKIP() when bandwidth property can't be queried (HMAT is not supported on the platform).
1 parent 5c83873 commit 65e799f

File tree

6 files changed

+302
-111
lines changed

6 files changed

+302
-111
lines changed

test/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ if(LINUX) # OS-specific functions are implemented only for Linux now
176176
add_umf_test(
177177
NAME memspace_highest_bandwidth
178178
SRCS memspaces/memspace_highest_bandwidth.cpp
179-
LIBS ${UMF_UTILS_FOR_TEST} ${LIBNUMA_LIBRARIES})
179+
LIBS ${UMF_UTILS_FOR_TEST} ${LIBNUMA_LIBRARIES} ${LIBHWLOC_LIBRARIES})
180180
endif()
181181

182182
if(UMF_BUILD_GPU_TESTS AND UMF_BUILD_LEVEL_ZERO_PROVIDER)

test/memspaces/memspace_helpers.hpp

Lines changed: 38 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
#include "base.hpp"
99
#include "memspace_internal.h"
1010
#include "memspaces/memspace_numa.h"
11+
#include "test_helpers.h"
1112

1213
#include <numa.h>
14+
#include <numaif.h>
1315
#include <umf/providers/provider_os_memory.h>
1416

1517
#define SIZE_4K (4096UL)
@@ -40,75 +42,44 @@ struct numaNodesTest : ::umf_test::test {
4042
unsigned long maxNodeId = 0;
4143
};
4244

43-
struct memspaceNumaTest : ::numaNodesTest {
44-
void SetUp() override {
45-
::numaNodesTest::SetUp();
46-
47-
umf_result_t ret = umfMemspaceCreateFromNumaArray(
48-
nodeIds.data(), nodeIds.size(), &hMemspace);
49-
ASSERT_EQ(ret, UMF_RESULT_SUCCESS);
50-
ASSERT_NE(hMemspace, nullptr);
51-
}
52-
53-
void TearDown() override {
54-
::numaNodesTest::TearDown();
55-
if (hMemspace) {
56-
umfMemspaceDestroy(hMemspace);
45+
///
46+
/// @brief Retrieves the memory policy information for \p ptr.
47+
/// @param ptr allocation pointer.
48+
/// @param maxNodeId maximum node id.
49+
/// @param mode [out] memory policy.
50+
/// @param boundNodeIds [out] node ids associated with the policy.
51+
/// @param allocNodeId [out] id of the node that allocated the memory.
52+
///
53+
void getAllocationPolicy(void *ptr, unsigned long maxNodeId, int &mode,
54+
std::vector<size_t> &boundNodeIds,
55+
size_t &allocNodeId) {
56+
const static unsigned bitsPerUlong = sizeof(unsigned long) * 8;
57+
58+
const unsigned nrUlongs = (maxNodeId + bitsPerUlong) / bitsPerUlong;
59+
std::vector<unsigned long> memNodeMasks(nrUlongs, 0);
60+
61+
int memMode = -1;
62+
// Get policy and the nodes associated with this policy.
63+
int ret = get_mempolicy(&memMode, memNodeMasks.data(),
64+
nrUlongs * bitsPerUlong, ptr, MPOL_F_ADDR);
65+
UT_ASSERTeq(ret, 0);
66+
mode = memMode;
67+
68+
UT_ASSERTeq(boundNodeIds.size(), 0);
69+
for (size_t i = 0; i <= maxNodeId; i++) {
70+
const size_t memNodeMaskIdx = ((i + bitsPerUlong) / bitsPerUlong) - 1;
71+
const auto &memNodeMask = memNodeMasks.at(memNodeMaskIdx);
72+
73+
if (memNodeMask && (1UL << (i % bitsPerUlong))) {
74+
boundNodeIds.emplace_back(i);
5775
}
5876
}
5977

60-
umf_memspace_handle_t hMemspace = nullptr;
61-
};
62-
63-
struct memspaceNumaProviderTest : ::memspaceNumaTest {
64-
void SetUp() override {
65-
::memspaceNumaTest::SetUp();
66-
67-
umf_result_t ret =
68-
umfMemoryProviderCreateFromMemspace(hMemspace, nullptr, &hProvider);
69-
ASSERT_EQ(ret, UMF_RESULT_SUCCESS);
70-
ASSERT_NE(hProvider, nullptr);
71-
}
72-
73-
void TearDown() override {
74-
::memspaceNumaTest::TearDown();
75-
76-
if (hProvider != nullptr) {
77-
umfMemoryProviderDestroy(hProvider);
78-
}
79-
}
80-
81-
umf_memory_provider_handle_t hProvider = nullptr;
82-
};
83-
84-
struct memspaceHostAllTest : ::numaNodesTest {
85-
void SetUp() override {
86-
::numaNodesTest::SetUp();
87-
88-
hMemspace = umfMemspaceHostAllGet();
89-
ASSERT_NE(hMemspace, nullptr);
90-
}
91-
92-
umf_memspace_handle_t hMemspace = nullptr;
93-
};
94-
95-
struct memspaceHostAllProviderTest : ::memspaceHostAllTest {
96-
void SetUp() override {
97-
::memspaceHostAllTest::SetUp();
98-
99-
umf_result_t ret =
100-
umfMemoryProviderCreateFromMemspace(hMemspace, nullptr, &hProvider);
101-
ASSERT_EQ(ret, UMF_RESULT_SUCCESS);
102-
ASSERT_NE(hProvider, nullptr);
103-
}
104-
105-
void TearDown() override {
106-
::memspaceHostAllTest::TearDown();
107-
108-
umfMemoryProviderDestroy(hProvider);
109-
}
110-
111-
umf_memory_provider_handle_t hProvider = nullptr;
112-
};
78+
// Get the node that allocated the memory at 'ptr'.
79+
int nodeId = -1;
80+
ret = get_mempolicy(&nodeId, nullptr, 0, ptr, MPOL_F_ADDR | MPOL_F_NODE);
81+
UT_ASSERTeq(ret, 0);
82+
allocNodeId = static_cast<size_t>(nodeId);
83+
}
11384

11485
#endif /* UMF_MEMSPACE_HELPERS_HPP */

test/memspaces/memspace_highest_bandwidth.cpp

Lines changed: 189 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,197 @@
77
#include "memspace_internal.h"
88
#include "test_helpers.h"
99

10-
#include <numa.h>
11-
#include <numaif.h>
10+
#include <hwloc.h>
11+
#include <thread>
1212
#include <umf/memspace.h>
1313

1414
using umf_test::test;
1515

16-
TEST_F(numaNodesTest, memspaceGet) {
17-
umf_memspace_handle_t hMemspace = umfMemspaceHighestBandwidthGet();
18-
UT_ASSERTne(hMemspace, nullptr);
16+
// In HWLOC v2.3.0, the 'hwloc_location_type_e' enum is defined inside an
17+
// 'hwloc_location' struct. In newer versions, this enum is defined globally.
18+
// To prevent compile errors in C++ tests related this scope change
19+
// 'hwloc_location_type_e' has been aliased.
20+
using hwloc_location_type_alias = decltype(hwloc_location::type);
21+
22+
static bool canQueryBandwidth(size_t nodeId) {
23+
hwloc_topology_t topology = nullptr;
24+
int ret = hwloc_topology_init(&topology);
25+
UT_ASSERTeq(ret, 0);
26+
ret = hwloc_topology_load(topology);
27+
UT_ASSERTeq(ret, 0);
28+
29+
hwloc_obj_t numaNode =
30+
hwloc_get_obj_by_type(topology, HWLOC_OBJ_NUMANODE, nodeId);
31+
UT_ASSERTne(numaNode, nullptr);
32+
33+
// Setup initiator structure.
34+
struct hwloc_location initiator;
35+
initiator.location.cpuset = numaNode->cpuset;
36+
initiator.type = hwloc_location_type_alias::HWLOC_LOCATION_TYPE_CPUSET;
37+
38+
hwloc_uint64_t value = 0;
39+
ret = hwloc_memattr_get_value(topology, HWLOC_MEMATTR_ID_BANDWIDTH,
40+
numaNode, &initiator, 0, &value);
41+
42+
hwloc_topology_destroy(topology);
43+
return (ret == 0);
44+
}
45+
46+
struct memspaceHighestBandwidthTest : ::numaNodesTest {
47+
void SetUp() override {
48+
::numaNodesTest::SetUp();
49+
50+
if (!canQueryBandwidth(nodeIds.front())) {
51+
GTEST_SKIP();
52+
}
53+
54+
hMemspace = umfMemspaceHighestBandwidthGet();
55+
ASSERT_NE(hMemspace, nullptr);
56+
}
57+
58+
umf_memspace_handle_t hMemspace = nullptr;
59+
};
60+
61+
struct memspaceHighestBandwidthProviderTest : ::memspaceHighestBandwidthTest {
62+
void SetUp() override {
63+
::memspaceHighestBandwidthTest::SetUp();
64+
65+
if (!canQueryBandwidth(nodeIds.front())) {
66+
GTEST_SKIP();
67+
}
68+
69+
umf_result_t ret =
70+
umfMemoryProviderCreateFromMemspace(hMemspace, nullptr, &hProvider);
71+
ASSERT_EQ(ret, UMF_RESULT_SUCCESS);
72+
ASSERT_NE(hProvider, nullptr);
73+
}
74+
75+
void TearDown() override {
76+
::memspaceHighestBandwidthTest::TearDown();
77+
78+
if (hProvider) {
79+
umfMemoryProviderDestroy(hProvider);
80+
}
81+
}
82+
83+
umf_memory_provider_handle_t hProvider = nullptr;
84+
};
85+
86+
TEST_F(memspaceHighestBandwidthTest, providerFromMemspace) {
87+
umf_memory_provider_handle_t hProvider = nullptr;
88+
umf_result_t ret =
89+
umfMemoryProviderCreateFromMemspace(hMemspace, nullptr, &hProvider);
90+
UT_ASSERTeq(ret, UMF_RESULT_SUCCESS);
91+
UT_ASSERTne(hProvider, nullptr);
92+
93+
umfMemoryProviderDestroy(hProvider);
94+
}
95+
96+
TEST_F(memspaceHighestBandwidthProviderTest, allocFree) {
97+
void *ptr = nullptr;
98+
size_t size = SIZE_4K;
99+
size_t alignment = 0;
100+
101+
umf_result_t ret = umfMemoryProviderAlloc(hProvider, size, alignment, &ptr);
102+
UT_ASSERTeq(ret, UMF_RESULT_SUCCESS);
103+
UT_ASSERTne(ptr, nullptr);
104+
105+
// Access the allocation, so that all the pages associated with it are
106+
// allocated on some NUMA node.
107+
memset(ptr, 0xFF, size);
108+
109+
ret = umfMemoryProviderFree(hProvider, ptr, size);
110+
UT_ASSERTeq(ret, UMF_RESULT_SUCCESS);
111+
}
112+
113+
static std::vector<int> getAllCpus() {
114+
std::vector<int> allCpus;
115+
for (int i = 0; i < numa_num_possible_cpus(); ++i) {
116+
if (numa_bitmask_isbitset(numa_all_cpus_ptr, i)) {
117+
allCpus.push_back(i);
118+
}
119+
}
120+
121+
return allCpus;
122+
}
123+
124+
#define MAX_NODES 512
125+
126+
TEST_F(memspaceHighestBandwidthProviderTest, allocLocalMt) {
127+
auto pinAllocValidate = [&](umf_memory_provider_handle_t hProvider,
128+
int cpu) {
129+
hwloc_topology_t topology = NULL;
130+
UT_ASSERTeq(hwloc_topology_init(&topology), 0);
131+
UT_ASSERTeq(hwloc_topology_load(topology), 0);
132+
133+
// Pin current thread to the provided CPU.
134+
hwloc_cpuset_t pinCpuset = hwloc_bitmap_alloc();
135+
UT_ASSERTeq(hwloc_bitmap_set(pinCpuset, cpu), 0);
136+
UT_ASSERTeq(
137+
hwloc_set_cpubind(topology, pinCpuset, HWLOC_CPUBIND_THREAD), 0);
138+
139+
// Confirm that the thread is pinned to the provided CPU.
140+
hwloc_cpuset_t curCpuset = hwloc_bitmap_alloc();
141+
UT_ASSERTeq(
142+
hwloc_get_cpubind(topology, curCpuset, HWLOC_CPUBIND_THREAD), 0);
143+
UT_ASSERT(hwloc_bitmap_isequal(curCpuset, pinCpuset));
144+
hwloc_bitmap_free(curCpuset);
145+
hwloc_bitmap_free(pinCpuset);
146+
147+
// Allocate some memory.
148+
const size_t size = SIZE_4K;
149+
const size_t alignment = 0;
150+
void *ptr = nullptr;
151+
152+
umf_result_t ret =
153+
umfMemoryProviderAlloc(hProvider, size, alignment, &ptr);
154+
UT_ASSERTeq(ret, UMF_RESULT_SUCCESS);
155+
UT_ASSERTne(ptr, nullptr);
156+
157+
// Access the allocation, so that all the pages associated with it are
158+
// allocated on some NUMA node.
159+
memset(ptr, 0xFF, size);
160+
161+
// Get the NUMA node responsible for this allocation.
162+
int mode = -1;
163+
std::vector<size_t> boundNodeIds;
164+
size_t allocNodeId = SIZE_MAX;
165+
getAllocationPolicy(ptr, maxNodeId, mode, boundNodeIds, allocNodeId);
166+
167+
// Get the CPUs associated with the specified NUMA node.
168+
hwloc_obj_t allocNodeObj =
169+
hwloc_get_obj_by_type(topology, HWLOC_OBJ_NUMANODE, allocNodeId);
170+
171+
unsigned nNodes = MAX_NODES;
172+
std::vector<hwloc_obj_t> localNodes(MAX_NODES);
173+
hwloc_location loc;
174+
loc.location.object = allocNodeObj,
175+
loc.type = hwloc_location_type_alias::HWLOC_LOCATION_TYPE_OBJECT;
176+
UT_ASSERTeq(hwloc_get_local_numanode_objs(topology, &loc, &nNodes,
177+
localNodes.data(), 0),
178+
0);
179+
UT_ASSERT(nNodes <= MAX_NODES);
180+
181+
// Confirm that the allocation from this thread was made to a local
182+
// NUMA node.
183+
UT_ASSERT(std::any_of(localNodes.begin(), localNodes.end(),
184+
[&allocNodeObj](hwloc_obj_t node) {
185+
return node == allocNodeObj;
186+
}));
187+
188+
ret = umfMemoryProviderFree(hProvider, ptr, size);
189+
UT_ASSERTeq(ret, UMF_RESULT_SUCCESS);
190+
191+
hwloc_topology_destroy(topology);
192+
};
193+
194+
const auto cpus = getAllCpus();
195+
std::vector<std::thread> threads;
196+
for (auto cpu : cpus) {
197+
threads.emplace_back(pinAllocValidate, hProvider, cpu);
198+
}
199+
200+
for (auto &thread : threads) {
201+
thread.join();
202+
}
19203
}

0 commit comments

Comments
 (0)