| 182 | |
| 183 | |
| 184 | qcomp* cpu_allocNumaArray(qindex length) { |
| 185 | #if ! NUMA_AWARE |
| 186 | return cpu_allocArray(length); |
| 187 | |
| 188 | #elif defined(_WIN32) |
| 189 | error_numaAllocOrDeallocAttemptedOnWindows(); |
| 190 | |
| 191 | #else |
| 192 | // we will divide array's memory into pages |
| 193 | long pageSize = cpu_getPageSize(); |
| 194 | qindex arraySize = length * sizeof(qcomp); // gauranteed no overflow |
| 195 | |
| 196 | // if entire array fits within a single page, alloc like normal |
| 197 | if (arraySize <= pageSize) |
| 198 | return cpu_allocArray(length); |
| 199 | |
| 200 | // otherwise we will bind pages across NUMA nodes |
| 201 | static int numNodes = numa_num_configured_nodes(); |
| 202 | if (numNodes < 1) |
| 203 | error_gettingNumNumaNodesFailed(); |
| 204 | |
| 205 | qindex numPages = getNumPagesToContainArray(pageSize, arraySize); |
| 206 | qindex numBytes = numPages * pageSize; // prior validation gaurantees no overflow |
| 207 | |
| 208 | // allocate memory, potentially more than arraySize (depending on page divisibility) |
| 209 | void *rawAddr = mmap(NULL, numBytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); |
| 210 | |
| 211 | // indicate memory alloc failure to caller (no NUMA-specific validation error message) |
| 212 | if (rawAddr == MAP_FAILED) |
| 213 | return nullptr; |
| 214 | |
| 215 | // if there is only a single NUMA node, then all memory access will occur within it |
| 216 | qcomp* outAddr = reinterpret_cast<qcomp*>(rawAddr); |
| 217 | if (numNodes == 1) |
| 218 | return outAddr; |
| 219 | |
| 220 | // otherwise, we bind continguous pages to NUMA nodes, distributing the pages |
| 221 | // attemptedly uniformly and spreading remaining pages maximally apart |
| 222 | qindex baseNumPagesPerNode = numPages / numNodes; // floors |
| 223 | qindex remainingNumPagesTotal = numPages % numNodes; |
| 224 | |
| 225 | // use integer type for safe address arithmetic below |
| 226 | uintptr_t offsetAddr = reinterpret_cast<uintptr_t>(rawAddr); |
| 227 | |
| 228 | for (int node=0, shift=numNodes; node < numNodes; ++node) { |
| 229 | |
| 230 | // decide number of pages to bind to NUMA node |
| 231 | shift -= remainingNumPagesTotal; |
| 232 | qindex numPagesInNode = baseNumPagesPerNode + (shift <= 0); |
| 233 | qindex numBytesInNode = numPagesInNode * pageSize; // validation prevents overflow |
| 234 | |
| 235 | // bind those pages from the offset address to the node (identified by mask) |
| 236 | unsigned long nodeMask = 1UL << node; |
| 237 | unsigned long numBitsInMask = 8 * sizeof(nodeMask); |
| 238 | void* nodeAddr = reinterpret_cast<void*>(offsetAddr); |
| 239 | long success = mbind(nodeAddr, numBytesInNode, MPOL_BIND, &nodeMask, numBitsInMask, 0); |
| 240 | |
| 241 | // treat bind failure as internal error (even though it can result from insufficient kernel mem), |
no test coverage detected