mirror of
https://gitea.wildfiregames.com/0ad/0ad.git
synced 2026-07-10 04:55:20 +00:00
45016e3980
also removed two critical sections (no longer needed due to thread-safe init) This was SVN commit r7745.
658 lines
18 KiB
C++
658 lines
18 KiB
C++
/* Copyright (c) 2010 Wildfire Games
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining
|
|
* a copy of this software and associated documentation files (the
|
|
* "Software"), to deal in the Software without restriction, including
|
|
* without limitation the rights to use, copy, modify, merge, publish,
|
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
* permit persons to whom the Software is furnished to do so, subject to
|
|
* the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included
|
|
* in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include "precompiled.h"
|
|
#include "lib/sysdep/numa.h"
|
|
|
|
#include "lib/bits.h" // round_up, PopulationCount
|
|
#include "lib/timer.h"
|
|
#include "lib/module_init.h"
|
|
#include "lib/sysdep/os_cpu.h"
|
|
#include "lib/sysdep/acpi.h"
|
|
#include "lib/sysdep/os/win/win.h"
|
|
#include "lib/sysdep/os/win/wutil.h"
|
|
#include "lib/sysdep/os/win/wcpu.h"
|
|
#include <Psapi.h>
|
|
|
|
#if ARCH_X86_X64
|
|
#include "lib/sysdep/arch/x86_x64/topology.h" // ApicIds
|
|
#endif
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// nodes
|
|
|
|
struct Node // POD
|
|
{
|
|
// (Windows doesn't guarantee node numbers are contiguous, so
|
|
// we associate them with contiguous indices in nodes[])
|
|
UCHAR nodeNumber;
|
|
|
|
u32 proximityDomainNumber;
|
|
uintptr_t processorMask;
|
|
};
|
|
|
|
static Node nodes[os_cpu_MaxProcessors];
|
|
static size_t numNodes;
|
|
|
|
static Node* AddNode()
|
|
{
|
|
debug_assert(numNodes < ARRAY_SIZE(nodes));
|
|
return &nodes[numNodes++];
|
|
}
|
|
|
|
static Node* FindNodeWithProcessorMask(uintptr_t processorMask)
|
|
{
|
|
for(size_t node = 0; node < numNodes; node++)
|
|
{
|
|
if(nodes[node].processorMask == processorMask)
|
|
return &nodes[node];
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static Node* FindNodeWithProcessor(size_t processor)
|
|
{
|
|
for(size_t node = 0; node < numNodes; node++)
|
|
{
|
|
if(IsBitSet(nodes[node].processorMask, processor))
|
|
return &nodes[node];
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
// cached results of FindNodeWithProcessor for each processor
|
|
static size_t processorsNode[os_cpu_MaxProcessors];
|
|
|
|
static void FillProcessorsNode()
|
|
{
|
|
for(size_t processor = 0; processor < os_cpu_NumProcessors(); processor++)
|
|
{
|
|
Node* node = FindNodeWithProcessor(processor);
|
|
if(node)
|
|
processorsNode[processor] = node-nodes;
|
|
else
|
|
debug_assert(0);
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Windows topology
|
|
|
|
static UCHAR HighestNodeNumber()
|
|
{
|
|
WUTIL_FUNC(pGetNumaHighestNodeNumber, BOOL, (PULONG));
|
|
WUTIL_IMPORT_KERNEL32(GetNumaHighestNodeNumber, pGetNumaHighestNodeNumber);
|
|
if(!pGetNumaHighestNodeNumber)
|
|
return 0; // NUMA not supported => only one node
|
|
|
|
ULONG highestNodeNumber;
|
|
const BOOL ok = pGetNumaHighestNodeNumber(&highestNodeNumber);
|
|
WARN_IF_FALSE(ok);
|
|
return (UCHAR)highestNodeNumber;
|
|
}
|
|
|
|
static void PopulateNodes()
|
|
{
|
|
WUTIL_FUNC(pGetNumaNodeProcessorMask, BOOL, (UCHAR, PULONGLONG));
|
|
WUTIL_IMPORT_KERNEL32(GetNumaNodeProcessorMask, pGetNumaNodeProcessorMask);
|
|
if(!pGetNumaNodeProcessorMask)
|
|
return;
|
|
|
|
DWORD_PTR processAffinity, systemAffinity;
|
|
{
|
|
const BOOL ok = GetProcessAffinityMask(GetCurrentProcess(), &processAffinity, &systemAffinity);
|
|
WARN_IF_FALSE(ok);
|
|
}
|
|
debug_assert(PopulationCount(processAffinity) <= PopulationCount(systemAffinity));
|
|
|
|
for(UCHAR nodeNumber = 0; nodeNumber <= HighestNodeNumber(); nodeNumber++)
|
|
{
|
|
ULONGLONG affinity;
|
|
{
|
|
const BOOL ok = pGetNumaNodeProcessorMask(nodeNumber, &affinity);
|
|
WARN_IF_FALSE(ok);
|
|
}
|
|
if(!affinity)
|
|
continue; // empty node, skip
|
|
|
|
Node* node = AddNode();
|
|
node->nodeNumber = nodeNumber;
|
|
node->processorMask = wcpu_ProcessorMaskFromAffinity(processAffinity, (DWORD_PTR)affinity);
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// ACPI SRAT topology
|
|
|
|
#if ARCH_X86_X64
|
|
|
|
#pragma pack(push, 1)
|
|
|
|
// fields common to Affinity* structures
|
|
struct AffinityHeader
|
|
{
|
|
u8 type;
|
|
u8 length; // size [bytes], including this header
|
|
};
|
|
|
|
struct AffinityAPIC
|
|
{
|
|
static const u8 type = 0;
|
|
|
|
AffinityHeader header;
|
|
u8 proximityDomainNumber0;
|
|
u8 apicId;
|
|
u32 flags;
|
|
u8 sapicId;
|
|
u8 proximityDomainNumber123[3];
|
|
u32 clockDomain;
|
|
|
|
u32 ProximityDomainNumber() const
|
|
{
|
|
// (this is the apparent result of backwards compatibility, ugh.)
|
|
u32 proximityDomainNumber;
|
|
memcpy(&proximityDomainNumber, &proximityDomainNumber123[0]-1, sizeof(proximityDomainNumber));
|
|
proximityDomainNumber &= ~0xFF;
|
|
proximityDomainNumber |= proximityDomainNumber0;
|
|
return proximityDomainNumber;
|
|
}
|
|
};
|
|
|
|
struct AffinityMemory
|
|
{
|
|
static const u8 type = 1;
|
|
|
|
AffinityHeader header;
|
|
u32 proximityDomainNumber;
|
|
u16 reserved1;
|
|
u64 baseAddress;
|
|
u64 length;
|
|
u32 reserved2;
|
|
u32 flags;
|
|
u64 reserved3;
|
|
};
|
|
|
|
// AffinityX2APIC omitted, since the APIC ID is sufficient for our purposes
|
|
|
|
// Static Resource Affinity Table
|
|
struct SRAT
|
|
{
|
|
AcpiTable header;
|
|
u32 reserved1;
|
|
u8 reserved2[8];
|
|
AffinityHeader affinities[1];
|
|
};
|
|
|
|
#pragma pack(pop)
|
|
|
|
template<class Affinity>
|
|
static const Affinity* DynamicCastFromHeader(const AffinityHeader* header)
|
|
{
|
|
if(header->type != Affinity::type)
|
|
return 0;
|
|
|
|
// sanity check: ensure no padding was inserted
|
|
debug_assert(header->length == sizeof(Affinity));
|
|
|
|
const Affinity* affinity = (const Affinity*)header;
|
|
if(!IsBitSet(affinity->flags, 0)) // not enabled
|
|
return 0;
|
|
|
|
return affinity;
|
|
}
|
|
|
|
static void PopulateProcessorMaskFromApicId(u32 apicId, uintptr_t& processorMask)
|
|
{
|
|
const u8* apicIds = ApicIds();
|
|
for(size_t processor = 0; processor < os_cpu_NumProcessors(); processor++)
|
|
{
|
|
if(apicIds[processor] == apicId)
|
|
{
|
|
processorMask |= Bit<uintptr_t>(processor);
|
|
return;
|
|
}
|
|
}
|
|
|
|
debug_assert(0); // APIC ID not found
|
|
}
|
|
|
|
struct ProximityDomain
|
|
{
|
|
uintptr_t processorMask;
|
|
// (AffinityMemory's fields are not currently needed)
|
|
};
|
|
|
|
typedef std::map<u32, ProximityDomain> ProximityDomains;
|
|
|
|
static ProximityDomains ExtractProximityDomainsFromSRAT(const SRAT* srat)
|
|
{
|
|
ProximityDomains proximityDomains;
|
|
|
|
for(const AffinityHeader* header = srat->affinities;
|
|
header < (const AffinityHeader*)(uintptr_t(srat)+srat->header.size);
|
|
header = (const AffinityHeader*)(uintptr_t(header) + header->length))
|
|
{
|
|
const AffinityAPIC* affinityAPIC = DynamicCastFromHeader<AffinityAPIC>(header);
|
|
if(affinityAPIC)
|
|
{
|
|
const u32 proximityDomainNumber = affinityAPIC->ProximityDomainNumber();
|
|
ProximityDomain& proximityDomain = proximityDomains[proximityDomainNumber];
|
|
PopulateProcessorMaskFromApicId(affinityAPIC->apicId, proximityDomain.processorMask);
|
|
}
|
|
}
|
|
|
|
return proximityDomains;
|
|
}
|
|
|
|
static void PopulateNodesFromProximityDomains(const ProximityDomains& proximityDomains)
|
|
{
|
|
for(ProximityDomains::const_iterator it = proximityDomains.begin(); it != proximityDomains.end(); ++it)
|
|
{
|
|
const u32 proximityDomainNumber = it->first;
|
|
const ProximityDomain& proximityDomain = it->second;
|
|
|
|
Node* node = FindNodeWithProcessorMask(proximityDomain.processorMask);
|
|
if(!node)
|
|
node = AddNode();
|
|
node->proximityDomainNumber = proximityDomainNumber;
|
|
node->processorMask = proximityDomain.processorMask;
|
|
}
|
|
}
|
|
|
|
#endif // #if ARCH_X86_X64
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
static ModuleInitState initState;
|
|
|
|
static LibError InitTopology()
|
|
{
|
|
PopulateNodes();
|
|
|
|
#if ARCH_X86_X64
|
|
const SRAT* srat = (const SRAT*)acpi_GetTable("SRAT");
|
|
if(srat)
|
|
{
|
|
const ProximityDomains proximityDomains = ExtractProximityDomainsFromSRAT(srat);
|
|
PopulateNodesFromProximityDomains(proximityDomains);
|
|
}
|
|
#endif
|
|
|
|
// neither OS nor ACPI information is available
|
|
if(numNodes == 0)
|
|
{
|
|
// add dummy node that contains all system processors
|
|
Node* node = AddNode();
|
|
node->nodeNumber = 0;
|
|
node->proximityDomainNumber = 0;
|
|
node->processorMask = os_cpu_ProcessorMask();
|
|
}
|
|
|
|
FillProcessorsNode();
|
|
return INFO::OK;
|
|
}
|
|
|
|
size_t numa_NumNodes()
|
|
{
|
|
(void)ModuleInit(&initState, InitTopology);
|
|
return numNodes;
|
|
}
|
|
|
|
size_t numa_NodeFromProcessor(size_t processor)
|
|
{
|
|
(void)ModuleInit(&initState, InitTopology);
|
|
debug_assert(processor < os_cpu_NumProcessors());
|
|
return processorsNode[processor];
|
|
}
|
|
|
|
uintptr_t numa_ProcessorMaskFromNode(size_t node)
|
|
{
|
|
(void)ModuleInit(&initState, InitTopology);
|
|
debug_assert(node < numNodes);
|
|
return nodes[node].processorMask;
|
|
}
|
|
|
|
static UCHAR NodeNumberFromNode(size_t node)
|
|
{
|
|
(void)ModuleInit(&initState, InitTopology);
|
|
debug_assert(node < numa_NumNodes());
|
|
return nodes[node].nodeNumber;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// memory info
|
|
|
|
size_t numa_AvailableMemory(size_t node)
|
|
{
|
|
// note: it is said that GetNumaAvailableMemoryNode sometimes incorrectly
|
|
// reports zero bytes. the actual cause may however be unexpected
|
|
// RAM configuration, e.g. not all slots filled.
|
|
WUTIL_FUNC(pGetNumaAvailableMemoryNode, BOOL, (UCHAR, PULONGLONG));
|
|
WUTIL_IMPORT_KERNEL32(GetNumaAvailableMemoryNode, pGetNumaAvailableMemoryNode);
|
|
if(pGetNumaAvailableMemoryNode)
|
|
{
|
|
const UCHAR nodeNumber = NodeNumberFromNode(node);
|
|
ULONGLONG availableBytes;
|
|
const BOOL ok = pGetNumaAvailableMemoryNode(nodeNumber, &availableBytes);
|
|
WARN_IF_FALSE(ok);
|
|
const size_t availableMiB = size_t(availableBytes / MiB);
|
|
return availableMiB;
|
|
}
|
|
// NUMA not supported - return available system memory
|
|
else
|
|
return os_cpu_MemoryAvailable();
|
|
}
|
|
|
|
|
|
#pragma pack(push, 1)
|
|
|
|
// ACPI System Locality Information Table
|
|
// (System Locality == Proximity Domain)
|
|
struct SLIT
|
|
{
|
|
AcpiTable header;
|
|
u64 numSystemLocalities;
|
|
u8 entries[1]; // numSystemLocalities*numSystemLocalities entries
|
|
};
|
|
|
|
#pragma pack(pop)
|
|
|
|
static double ReadRelativeDistanceFromSLIT(const SLIT* slit)
|
|
{
|
|
const size_t n = slit->numSystemLocalities;
|
|
debug_assert(slit->header.size == sizeof(SLIT)-sizeof(slit->entries)+n*n);
|
|
// diagonals are specified to be 10
|
|
for(size_t i = 0; i < n; i++)
|
|
debug_assert(slit->entries[i*n+i] == 10);
|
|
// entries = relativeDistance * 10
|
|
return *std::max_element(slit->entries, slit->entries+n*n) / 10.0;
|
|
}
|
|
|
|
// @return ratio between max/min time required to access one node's
|
|
// memory from each processor.
|
|
static double MeasureRelativeDistance()
|
|
{
|
|
// allocate memory on one node
|
|
const size_t size = 16*MiB;
|
|
shared_ptr<u8> buffer((u8*)numa_AllocateOnNode(size, 0), numa_Deleter<u8>());
|
|
|
|
const uintptr_t previousProcessorMask = os_cpu_SetThreadAffinityMask(os_cpu_ProcessorMask());
|
|
|
|
double minTime = 1e10, maxTime = 0.0;
|
|
for(size_t node = 0; node < numa_NumNodes(); node++)
|
|
{
|
|
const uintptr_t processorMask = numa_ProcessorMaskFromNode(node);
|
|
os_cpu_SetThreadAffinityMask(processorMask);
|
|
|
|
const double startTime = timer_Time();
|
|
memset(buffer.get(), 0, size);
|
|
const double elapsedTime = timer_Time() - startTime;
|
|
|
|
minTime = std::min(minTime, elapsedTime);
|
|
maxTime = std::max(maxTime, elapsedTime);
|
|
}
|
|
|
|
(void)os_cpu_SetThreadAffinityMask(previousProcessorMask);
|
|
|
|
return maxTime / minTime;
|
|
}
|
|
|
|
static double relativeDistance;
|
|
|
|
static LibError InitRelativeDistance()
|
|
{
|
|
// early-out for non-NUMA systems (saves some time)
|
|
if(numa_NumNodes() == 1)
|
|
{
|
|
relativeDistance = 1.0;
|
|
return INFO::OK;
|
|
}
|
|
|
|
// trust values reported by the BIOS, if available
|
|
const SLIT* slit = (const SLIT*)acpi_GetTable("SLIT");
|
|
if(slit)
|
|
relativeDistance = ReadRelativeDistanceFromSLIT(slit);
|
|
else
|
|
relativeDistance = MeasureRelativeDistance();
|
|
|
|
debug_assert(relativeDistance >= 1.0);
|
|
debug_assert(relativeDistance <= 3.0); // (Microsoft guideline for NUMA systems)
|
|
return INFO::OK;
|
|
}
|
|
|
|
double numa_Factor()
|
|
{
|
|
static ModuleInitState initState;
|
|
(void)ModuleInit(&initState, InitRelativeDistance);
|
|
return relativeDistance;
|
|
}
|
|
|
|
|
|
static bool IsMemoryInterleaved()
|
|
{
|
|
if(numa_NumNodes() == 1)
|
|
return false;
|
|
|
|
if(!acpi_GetTable("FACP")) // no ACPI tables available
|
|
return false; // indeterminate, assume not interleaved
|
|
|
|
if(acpi_GetTable("SRAT")) // present iff not interleaved
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool isMemoryInterleaved;
|
|
|
|
static LibError InitMemoryInterleaved()
|
|
{
|
|
isMemoryInterleaved = IsMemoryInterleaved();
|
|
return INFO::OK;
|
|
}
|
|
|
|
bool numa_IsMemoryInterleaved()
|
|
{
|
|
static ModuleInitState initState;
|
|
(void)ModuleInit(&initState, InitMemoryInterleaved);
|
|
return isMemoryInterleaved;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// allocator
|
|
|
|
static bool largePageAllocationTookTooLong = false;
|
|
|
|
static bool ShouldUseLargePages(LargePageDisposition disposition, size_t allocationSize)
|
|
{
|
|
// can't, OS does not support large pages
|
|
if(os_cpu_LargePageSize() == 0)
|
|
return false;
|
|
|
|
// overrides
|
|
if(disposition == LPD_NEVER)
|
|
return false;
|
|
if(disposition == LPD_ALWAYS)
|
|
return true;
|
|
|
|
// default disposition: use a heuristic
|
|
{
|
|
// allocation is rather small and would "only" use half of the
|
|
// TLBs for its pages.
|
|
if(allocationSize < 64/2 * os_cpu_PageSize())
|
|
return false;
|
|
|
|
// pre-Vista Windows OSes attempt to cope with page fragmentation by
|
|
// trimming the working set of all processes, thus swapping them out,
|
|
// and waiting for contiguous regions to appear. this is terribly
|
|
// slow (multiple seconds), hence the following heuristics:
|
|
if(wutil_WindowsVersion() < WUTIL_VERSION_VISTA)
|
|
{
|
|
// a previous attempt already took too long.
|
|
if(largePageAllocationTookTooLong)
|
|
return false;
|
|
|
|
// if there's not plenty of free memory, then memory is surely
|
|
// already fragmented.
|
|
if(os_cpu_MemoryAvailable() < 2000) // 2 GB
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void* numa_Allocate(size_t size, LargePageDisposition largePageDisposition, size_t* ppageSize)
|
|
{
|
|
void* mem = 0;
|
|
|
|
// try allocating with large pages (reduces TLB misses)
|
|
if(ShouldUseLargePages(largePageDisposition, size))
|
|
{
|
|
const size_t largePageSize = os_cpu_LargePageSize();
|
|
const size_t paddedSize = round_up(size, largePageSize); // required by MEM_LARGE_PAGES
|
|
// note: this call can take SECONDS, which is why several checks are
|
|
// undertaken before we even try. these aren't authoritative, so we
|
|
// at least prevent future attempts if it takes too long.
|
|
const double startTime = timer_Time();
|
|
mem = VirtualAlloc(0, paddedSize, MEM_RESERVE|MEM_COMMIT|MEM_LARGE_PAGES, PAGE_READWRITE);
|
|
if(ppageSize)
|
|
*ppageSize = largePageSize;
|
|
const double elapsedTime = timer_Time() - startTime;
|
|
debug_printf(L"TIMER| NUMA large page allocation: %g\n", elapsedTime);
|
|
if(elapsedTime > 1.0)
|
|
largePageAllocationTookTooLong = true;
|
|
}
|
|
|
|
// try (again) with regular pages
|
|
if(!mem)
|
|
{
|
|
mem = VirtualAlloc(0, size, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
|
|
if(ppageSize)
|
|
*ppageSize = os_cpu_PageSize();
|
|
}
|
|
|
|
// all attempts failed - we're apparently out of memory.
|
|
if(!mem)
|
|
throw std::bad_alloc();
|
|
|
|
return mem;
|
|
}
|
|
|
|
|
|
static bool VerifyPages(void* mem, size_t size, size_t pageSize, size_t node)
|
|
{
|
|
WUTIL_FUNC(pQueryWorkingSetEx, BOOL, (HANDLE, PVOID, DWORD));
|
|
WUTIL_IMPORT_KERNEL32(QueryWorkingSetEx, pQueryWorkingSetEx);
|
|
if(!pQueryWorkingSetEx)
|
|
return true; // can't do anything
|
|
|
|
#if WINVER >= 0x600
|
|
size_t largePageSize = os_cpu_LargePageSize();
|
|
debug_assert(largePageSize != 0); // this value is needed for later
|
|
|
|
// retrieve attributes of all pages constituting mem
|
|
const size_t numPages = (size + pageSize-1) / pageSize;
|
|
PSAPI_WORKING_SET_EX_INFORMATION* wsi = new PSAPI_WORKING_SET_EX_INFORMATION[numPages];
|
|
for(size_t i = 0; i < numPages; i++)
|
|
wsi[i].VirtualAddress = (u8*)mem + i*pageSize;
|
|
pQueryWorkingSetEx(GetCurrentProcess(), wsi, DWORD(sizeof(PSAPI_WORKING_SET_EX_INFORMATION)*numPages));
|
|
|
|
// ensure each is valid and allocated on the correct node
|
|
for(size_t i = 0; i < numPages; i++)
|
|
{
|
|
const PSAPI_WORKING_SET_EX_BLOCK& attributes = wsi[i].VirtualAttributes;
|
|
if(!attributes.Valid)
|
|
return false;
|
|
if((attributes.LargePage != 0) != (pageSize == largePageSize))
|
|
{
|
|
debug_printf(L"NUMA: is not a large page\n");
|
|
return false;
|
|
}
|
|
if(attributes.Node != node)
|
|
{
|
|
debug_printf(L"NUMA: allocated from remote node\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
delete[] wsi;
|
|
#else
|
|
UNUSED2(mem);
|
|
UNUSED2(size);
|
|
UNUSED2(pageSize);
|
|
UNUSED2(node);
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void* numa_AllocateOnNode(size_t node, size_t size, LargePageDisposition largePageDisposition, size_t* ppageSize)
|
|
{
|
|
debug_assert(node < numa_NumNodes());
|
|
|
|
// see if there will be enough memory (non-authoritative, for debug purposes only)
|
|
{
|
|
const size_t sizeMiB = size/MiB;
|
|
const size_t availableMiB = numa_AvailableMemory(node);
|
|
if(availableMiB < sizeMiB)
|
|
debug_printf(L"NUMA: warning: node reports insufficient memory (%d vs %d MB)\n", availableMiB, sizeMiB);
|
|
}
|
|
|
|
size_t pageSize; // (used below even if ppageSize is zero)
|
|
void* const mem = numa_Allocate(size, largePageDisposition, &pageSize);
|
|
if(ppageSize)
|
|
*ppageSize = pageSize;
|
|
|
|
// we can't use VirtualAllocExNuma - it's only available in Vista and Server 2008.
|
|
// workaround: fault in all pages now to ensure they are allocated from the
|
|
// current node, then verify page attributes.
|
|
// (note: VirtualAlloc's MEM_COMMIT only maps virtual pages and does not
|
|
// actually allocate page frames. Windows XP uses a first-touch heuristic -
|
|
// the page will be taken from the node whose processor caused the fault.
|
|
// Windows Vista allocates on the "preferred" node, so affinity should be
|
|
// set such that this thread is running on <node>.)
|
|
const uintptr_t previousProcessorMask = os_cpu_SetThreadAffinityMask(numa_ProcessorMaskFromNode(node));
|
|
memset(mem, 0, size);
|
|
(void)os_cpu_SetThreadAffinityMask(previousProcessorMask);
|
|
|
|
VerifyPages(mem, size, pageSize, node);
|
|
|
|
return mem;
|
|
}
|
|
|
|
|
|
void numa_Deallocate(void* mem)
|
|
{
|
|
VirtualFree(mem, 0, MEM_RELEASE);
|
|
}
|