Fix MingW build
[alexxy/gromacs.git] / src / external / thread_mpi / src / numa_malloc.c
1
2
3 #ifdef HAVE_TMPI_CONFIG_H
4 #include "tmpi_config.h"
5 #endif
6
7
8 #ifdef HAVE_CONFIG_H
9 #include "config.h"
10 #endif
11
12 #ifdef THREAD_WINDOWS
13     #ifdef __MINGW32__
14        #define _WIN32_WINNT 0x0601 /* Windows 7*/
15     #endif
16 #endif
17
18 #ifdef HAVE_UNISTD_H
19 #include <unistd.h>
20 #endif
21
22
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #ifdef WITH_DMALLOC
27 #include <dmalloc.h>
28 #endif
29
30 #ifndef THREAD_WINDOWS
31
32 /* We don't have specific NUMA aware allocators: */
33
34 void *tMPI_Malloc_local(size_t size)
35 {
36     return malloc(size);
37 }
38
39 void *tMPI_Calloc_local(size_t nmemb, size_t size)
40 {
41     return calloc(nmemb, size);
42 }
43
44 void *tMPI_Realloc_local(void *ptr, size_t size)
45 {
46     return realloc(ptr, size);
47 }
48
49 int tMPI_Free_numa(void *ptr)
50 {
51     free(ptr);
52     return 0; /* we don't detect errors here */
53 }
54
55 #else
56
57 #define TMPI_NUMA_MALLOC
58
59 /*
60     Windows NUMA memory allocation support.
61
62     NUMA support is implemented to maximize the chance that memory access
63     patterns remain Local to the NUMA node.  This avoids penalties accessing
64     "remote" memory.
65     An important assumption here is that code paths which allocate and
66     reallocate heap blocks are likely to be accessing that allocated memory
67     on the same NUMA node.
68     Testing has shown the above criteria to be met, yielding gains of > 15%
69     when on Windows with NUMA hardware.
70
71     The high level approach is:
72     1. Use a separate heap per NUMA node.  This reduces heap contention, steers
73        allocations to the local NUMA node, and avoids re-use of freed heap
74        blocks across (remote) NUMA nodes.
75     2. Allocate each heap locally to each NUMA node, such that heap control
76        structures are on the NUMA node accessing the heap.
77     3. During realloc operations, transfer the new block to the local NUMA
78        node, if appropriate.  This is a rare case when thread affinity and
79        access patterns are correct.
80     4. Use GetProcAddress() to obtain function pointers to functions that are
81        operating system version dependent, to allow maximum binary
82        compatibility.
83
84     Scott Field (sfield@microsoft.com)      Jan-2011
85  */
86
87 #include <windows.h>
88
89 /*
90     __declspec(align()) may not be supported by all compilers, so define the
91     size of the structure manually to force alignment
92     note that HeapAlloc() already returns aligned blocks.
93     typedef __declspec(align(32)) struct ...
94  */
95 typedef struct {
96     DWORD  dwMagic;
97     HANDLE hHeap;               /* 8 */
98     size_t cbAllocationSize;    /* 8 */
99     ULONG  ProcessorNumber;     /* processor number at time of allocation
100                                    (development/testing) */
101     USHORT NodeNumber;          /* NUMA node number at time of allocation
102                                    (development/testing) */
103 } HEAPHEADER, *PHEAPHEADER;
104
105 #define HEAP_NUMA_MAGIC     0x05AF0777
106 #define HEAPHEADER_SIZE     (32)
107
108 #ifdef C_ASSERT
109 /* fail compile if size of HEAPHEADER exceeds pre-defined value */
110 C_ASSERT(sizeof(HEAPHEADER) <= HEAPHEADER_SIZE);
111 #endif
112
113 /* function prototypes and variables to support obtaining function
114    addresses dynamically -- supports down-level operating systems */
115
116 typedef BOOL (WINAPI *func_GetNumaHighestNodeNumber_t)( PULONG HighestNodeNumber );
117 typedef BOOL (WINAPI *func_GetNumaProcessorNodeEx_t)( PPROCESSOR_NUMBER Processor, PUSHORT NodeNumber );
118 typedef VOID (WINAPI *func_GetCurrentProcessorNumberEx_t)( PPROCESSOR_NUMBER ProcNumber );
119
120 func_GetNumaHighestNodeNumber_t             smalloc_GetNumaHighestNodeNumber;         /* WinXP SP2, WinXP64, WinSrv 2003 */
121 func_GetNumaProcessorNodeEx_t               smalloc_GetNumaProcessorNodeEx;           /* Windows 7, WinSrv 2008R2 */
122 func_GetCurrentProcessorNumberEx_t          smalloc_GetCurrentProcessorNumberEx;      /* Windows 7, WinSrv 2008R2 */
123
124 #define NUMA_STATUS_UNKNOWN     (0)
125 #define NUMA_STATUS_NOT_NUMA    (1)
126 #define NUMA_STATUS_NUMA        (2)
127
128 DWORD   g_dwTlsHeap;    /* TLS slot used for preferred heap handle */
129 HANDLE *g_hHeap;        /* array of heap handles */
130 ULONG   g_ulNumaStatus; /* 0 = unknown, 1 = not NUMA, 2 = NUMA */
131
132 VOID
133 InitNumaHeapSupport(
134         VOID
135         )
136 {
137     HMODULE hModKernel32;   /* module handle to kernel32.dll -- we already
138                                reference it, so it's already loaded */
139     ULONG   ulNumaHighestNodeNumber;
140
141     /* grab the addresses for the NUMA functions.
142        It's fine if there is a race condition reaching this routine */
143     hModKernel32 = GetModuleHandleA("kernel32.dll");
144     if (hModKernel32 == NULL)
145     {
146         g_ulNumaStatus = NUMA_STATUS_NOT_NUMA;
147         return;
148     }
149
150     smalloc_GetNumaHighestNodeNumber    = (func_GetNumaHighestNodeNumber_t)GetProcAddress( hModKernel32, "GetNumaHighestNodeNumber" );
151     smalloc_GetCurrentProcessorNumberEx = (func_GetCurrentProcessorNumberEx_t)GetProcAddress( hModKernel32, "GetCurrentProcessorNumberEx" );
152     smalloc_GetNumaProcessorNodeEx      = (func_GetNumaProcessorNodeEx_t)GetProcAddress( hModKernel32, "GetNumaProcessorNodeEx" );
153
154     if ( (smalloc_GetNumaHighestNodeNumber == NULL) ||
155          (smalloc_GetCurrentProcessorNumberEx == NULL) ||
156          (smalloc_GetNumaProcessorNodeEx == NULL) )
157     {
158         g_ulNumaStatus = NUMA_STATUS_NOT_NUMA;
159         return;
160     }
161
162     /* determine how many NUMA nodes are present */
163
164     if (!smalloc_GetNumaHighestNodeNumber(&ulNumaHighestNodeNumber) ||
165         (ulNumaHighestNodeNumber == 0) )
166     {
167         g_ulNumaStatus = NUMA_STATUS_NOT_NUMA;
168         return;
169     }
170
171     /* handle deferred creation of TLS slot.
172        note: this could be moved to one-time init path.
173        failures here result in assuming the system is not NUMA.
174      */
175
176     if (g_dwTlsHeap == 0)
177     {
178         DWORD dwTlsHeap = TlsAlloc();
179         DWORD dwPriorValue;
180
181         if (dwTlsHeap == TLS_OUT_OF_INDEXES)
182         {
183             g_ulNumaStatus = NUMA_STATUS_NOT_NUMA;
184             return;
185         }
186
187         dwPriorValue = (DWORD)InterlockedCompareExchange(
188                     (LONG volatile *)&g_dwTlsHeap,
189                     (LONG) dwTlsHeap,
190                     0
191                     );
192
193         if (dwPriorValue != 0)
194         {
195             TlsFree( dwTlsHeap );
196         }
197     }
198
199     /* handle deferred creation of heap handle array.
200        note: this could be moved to one-time init path.
201      */
202
203     if (g_hHeap == NULL)
204     {
205         HANDLE *hHeapNew;
206         HANDLE *hPriorValue;
207
208         /* allocate an array to contain a heap handle for each NUMA node */
209         hHeapNew = (HANDLE*)HeapAlloc(
210                     GetProcessHeap(),
211                     HEAP_ZERO_MEMORY,
212                     sizeof(HANDLE) * (ulNumaHighestNodeNumber+1)
213                     );
214
215         if (hHeapNew == NULL)
216         {
217             g_ulNumaStatus = NUMA_STATUS_NOT_NUMA;
218             return;
219         }
220
221 #if defined(WIN64) || defined( _WIN64 )
222         hPriorValue = (HANDLE *)InterlockedCompareExchange64(
223                     (LONGLONG volatile *)&g_hHeap,
224                     (LONGLONG) hHeapNew,
225                     0
226                     );
227 #else
228         hPriorValue = (HANDLE *)InterlockedCompareExchange(
229                     (LONG volatile *)&g_hHeap,
230                     (LONG) hHeapNew,
231                     0
232                     );
233 #endif
234
235         if (hPriorValue != NULL)
236         {
237             HeapFree(GetProcessHeap(), 0, hHeapNew);
238         }
239     }
240
241     /* indicate system is NUMA */
242     g_ulNumaStatus = NUMA_STATUS_NUMA;
243
244     return;
245 }
246
247 HANDLE
248 ReturnHeapHandle(
249         VOID
250         )
251 {
252     HANDLE           hHeap;                     /* preferred heap handle to
253                                                    return to caller */
254     PROCESSOR_NUMBER CurrentProcessorNumber;    /* processor number associated
255                                                    with calling thread */
256     USHORT           CurrentNumaNodeNumber;     /* NUMA node number assocaited
257                                                    with calling thread */
258
259     /* determine NUMA status of system. */
260
261     if (g_ulNumaStatus == NUMA_STATUS_UNKNOWN)
262     {
263         InitNumaHeapSupport();
264         if (g_ulNumaStatus == NUMA_STATUS_NOT_NUMA)
265         {
266             return GetProcessHeap();
267         }
268     }
269     else if (g_ulNumaStatus == NUMA_STATUS_NOT_NUMA)
270     {
271         /* not NUMA, return the process heap handle */
272         return GetProcessHeap();
273     }
274
275
276     /* return the preferred heap handle from the TLS slot, if set.
277        This is the commonly taken path. */
278
279     hHeap = (HANDLE)TlsGetValue( g_dwTlsHeap );
280
281     if (hHeap != NULL)
282     {
283         return hHeap;
284     }
285
286
287     /* preferred heap handle not yet set.
288        determine the numa node we're executing on, and create a heap which
289        is assigned to this node.
290        one (soft) assumption that is made here is that thread affinity has
291        been set such that threads do not move between NUMA nodes.
292      */
293
294     smalloc_GetCurrentProcessorNumberEx(&CurrentProcessorNumber);
295
296     if (!smalloc_GetNumaProcessorNodeEx(&CurrentProcessorNumber, &CurrentNumaNodeNumber))
297     {
298         /* GetNumaProcessorNodeEx() can fail on WOW64/32bit if invoked
299            against processor numbers > 32.
300            this should never be reached for 64bit builds.
301          */
302         CurrentNumaNodeNumber = 0;
303     }
304
305
306     /* check if the NUMA node array slot already contains a heap */
307     /* CurrentNumaNodeNumber cannot execeed count of heaps, as NUMA nodes
308        cannot be added. */
309
310     hHeap = g_hHeap[ CurrentNumaNodeNumber ];
311
312     if (hHeap == NULL)
313     {
314         HANDLE hHeapPrior = NULL;
315         ULONG  ulOption   = 2; /* HEAP_LFH */
316
317         /* create a heap for this numa node
318            defer creating the heap - while running on each node - to ensure
319            the heap control structures get created on the local NUMA node.
320          */
321
322         hHeap = HeapCreate(0, 0, 0);
323
324         if (hHeap == NULL)
325         {
326             /* just return the process heap.  We'll try to create a heap
327                again next time */
328             return GetProcessHeap();
329         }
330
331         /* make the new heap a low-fragmentation heap */
332
333         HeapSetInformation(
334                 hHeap,
335                 0,          /* HeapCompatibilityInformation */
336                 &ulOption,
337                 sizeof(ulOption)
338                 );
339
340         /* set the array slot entry for this NUMA node to contain the newly
341            allocated heap */
342
343         hHeapPrior = (HANDLE)InterlockedCompareExchangePointer(&(g_hHeap[CurrentNumaNodeNumber]), hHeap, NULL);
344         if (hHeapPrior != NULL)
345         {
346             HeapDestroy( hHeap );
347             hHeap = hHeapPrior;
348         }
349     }
350
351     /* we reached here since there was no heap assigned to the TLS slot.
352        Assign it. */
353     TlsSetValue(g_dwTlsHeap, hHeap);
354
355     return hHeap;
356 }
357
358
359
360
361 void *tMPI_Malloc_local(size_t size)
362 {
363     HANDLE         hHeap;
364     unsigned char *ptr;
365     size_t         new_size;
366     HEAPHEADER    *phdr;
367
368     hHeap = ReturnHeapHandle();
369
370     new_size = size + HEAPHEADER_SIZE;
371
372     ptr = (unsigned char *)HeapAlloc( hHeap, 0, new_size );
373
374     if (ptr == NULL)
375     {
376         return NULL;
377     }
378
379     phdr = (HEAPHEADER*)ptr;
380
381     phdr->dwMagic          = HEAP_NUMA_MAGIC;
382     phdr->hHeap            = hHeap;     /* track the heap handle for realloc
383                                            and free */
384     phdr->cbAllocationSize = new_size;  /* track the allocation size for
385                                            realloc and debugging */
386
387     return ( ptr + HEAPHEADER_SIZE );
388 }
389
390
391 void *tMPI_Calloc_local(size_t nelem, size_t elsize)
392 {
393     void  *ptr;
394     size_t size = nelem * elsize;
395
396     ptr = tMPI_Malloc_local(size);
397
398     if (ptr != NULL)
399     {
400         memset(ptr, 0, size);
401     }
402
403     return ptr;
404 }
405
406
407 void *tMPI_Realloc_local(void *ptr, size_t size)
408 {
409     HANDLE         hHeap;
410     HEAPHEADER    *phdr;
411     unsigned char *new_ptr;
412     size_t         new_size;
413
414     /* calculate the allocation address and check for presence of the hint
415        which indicates this was allocated by our allocator.
416      */
417
418     phdr = (HEAPHEADER*)((unsigned char*)ptr - HEAPHEADER_SIZE);
419
420     if (phdr->dwMagic != HEAP_NUMA_MAGIC)
421     {
422         /* TODO: call tMPI_Error() */
423         /*gmx_fatal(errno,__FILE__,__LINE__,
424            "Invalid Heap header during realloc. %p", ptr);*/
425
426         return NULL;
427     }
428
429     /* calculate size of new/realloc'd block.
430      */
431
432     new_size = size + HEAPHEADER_SIZE;
433
434     /* if the NUMA Node changed between the initial allocation and the
435        reallocation, copy the memory to an allocation on the new local node:
436        we assume the new realloc'd block is more likely to be manipulated by
437        the current thread which is calling realloc.
438        the simple way to detect this condition is to compare the preferred heap
439        handle with the heap handle stored in the current memory block.
440      */
441
442     hHeap = ReturnHeapHandle();
443
444     if (hHeap != phdr->hHeap)
445     {
446         new_ptr = HeapAlloc( hHeap, 0, new_size );
447
448         /* if the new allocation succeeded, copy the buffer and free the
449            original buffer.
450          */
451
452         if (new_ptr != NULL)
453         {
454             size_t copy_size;
455
456             /* the realloc can be larger or smaller than the original
457                allocation size.
458              */
459
460             if (new_size > phdr->cbAllocationSize)
461             {
462                 copy_size = phdr->cbAllocationSize;
463             }
464             else
465             {
466                 copy_size = new_size;
467             }
468
469             /* copy the current memory block contents into the newly allocated
470                buffer, and then free the original buffer.
471              */
472
473             memcpy( new_ptr, phdr, copy_size );
474
475             HeapFree( phdr->hHeap, 0, phdr );
476         }
477
478     }
479     else
480     {
481
482         /* NodeNumber of existing allocation matches current node number.
483            realloc from the heap associated with the existing allocation.
484          */
485
486         hHeap = phdr->hHeap;
487
488         new_ptr = HeapReAlloc(
489                     hHeap,
490                     0,
491                     phdr,
492                     new_size
493                     );
494     }
495
496     if (new_ptr == NULL)
497     {
498         return NULL;
499     }
500
501     phdr                   = (HEAPHEADER*)new_ptr;
502     phdr->cbAllocationSize = new_size;  /* update allocation size to match
503                                            realloc */
504     phdr->hHeap            = hHeap;
505
506     return ( new_ptr + HEAPHEADER_SIZE );
507 }
508
509
510 int tMPI_Free_numa(void *ptr)
511 {
512     HEAPHEADER *phdr;
513
514     /* caller doesn't call us on ptr == NULL case, so we don't need to
515        check that here. */
516
517     phdr = (HEAPHEADER*)((unsigned char*)ptr - HEAPHEADER_SIZE);
518
519     /* this check should happen in __try / __except block until the
520        mis-matched free/sfree calls are fixed, but this is here primarilly
521        for debugging and catching mis-matched memory alloc and free references.
522      */
523
524     if (phdr->dwMagic != HEAP_NUMA_MAGIC)
525     {
526         /* ptr is leaked here, rather than faulting in the allocator.
527            this is done in order to track mis-matched alloc/free calls.
528          */
529         return 1;
530     }
531
532     phdr->dwMagic = 0;
533
534     HeapFree( phdr->hHeap, 0, phdr );
535
536     return 0;
537 }
538
539 #endif /* NUMA allocation functions for (_WIN32 || _WIN64) */