Code beautification with uncrustify
[alexxy/gromacs.git] / src / gromacs / gmxlib / thread_mpi / 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
13 #ifdef HAVE_UNISTD_H
14 #include <unistd.h>
15 #endif
16
17
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #ifdef WITH_DMALLOC
22 #include "dmalloc.h"
23 #endif
24
25
26 #if !(defined(WIN32) || defined( _WIN32 ) || defined(WIN64) || defined( _WIN64 )) || defined (__CYGWIN__) || defined (__CYGWIN32__)
27
28
29 /* We don't have specific NUMA aware allocators: */
30
31 void *tMPI_Malloc_local(size_t size)
32 {
33     return malloc(size);
34 }
35
36 void *tMPI_Calloc_local(size_t nmemb, size_t size)
37 {
38     return calloc(nmemb, size);
39 }
40
41 void *tMPI_Realloc_local(void *ptr, size_t size)
42 {
43     return realloc(ptr, size);
44 }
45
46 int tMPI_Free_numa(void *ptr)
47 {
48     free(ptr);
49     return 0; /* we don't detect errors here */
50 }
51
52 #else
53
54 #define TMPI_NUMA_MALLOC
55
56 /*
57     Windows NUMA memory allocation support.
58
59     NUMA support is implemented to maximize the chance that memory access
60     patterns remain Local to the NUMA node.  This avoids penalties accessing
61     "remote" memory.
62     An important assumption here is that code paths which allocate and
63     reallocate heap blocks are likely to be accessing that allocated memory
64     on the same NUMA node.
65     Testing has shown the above criteria to be met, yielding gains of > 15%
66     when on Windows with NUMA hardware.
67
68     The high level approach is:
69     1. Use a separate heap per NUMA node.  This reduces heap contention, steers
70        allocations to the local NUMA node, and avoids re-use of freed heap
71        blocks across (remote) NUMA nodes.
72     2. Allocate each heap locally to each NUMA node, such that heap control
73        structures are on the NUMA node accessing the heap.
74     3. During realloc operations, transfer the new block to the local NUMA
75        node, if appropriate.  This is a rare case when thread affinity and
76        access patterns are correct.
77     4. Use GetProcAddress() to obtain function pointers to functions that are
78        operating system version dependent, to allow maximum binary
79        compatibility.
80
81     Scott Field (sfield@microsoft.com)      Jan-2011
82  */
83
84 //#define _WIN32_WINNT 0x0601
85 #include <windows.h>
86
87
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         hPriorValue = (HANDLE *)InterlockedCompareExchange(
222                     (LONG volatile *)&g_hHeap,
223                     (LONG) hHeapNew,
224                     0
225                     );
226
227         if (hPriorValue != NULL)
228         {
229             HeapFree(GetProcessHeap(), 0, hHeapNew);
230         }
231     }
232
233     /* indicate system is NUMA */
234     g_ulNumaStatus = NUMA_STATUS_NUMA;
235
236     return;
237 }
238
239 HANDLE
240 ReturnHeapHandle(
241         VOID
242         )
243 {
244     HANDLE           hHeap;                     /* preferred heap handle to
245                                                    return to caller */
246     PROCESSOR_NUMBER CurrentProcessorNumber;    /* processor number associated
247                                                    with calling thread */
248     USHORT           CurrentNumaNodeNumber;     /* NUMA node number assocaited
249                                                    with calling thread */
250
251     /* determine NUMA status of system. */
252
253     if (g_ulNumaStatus == NUMA_STATUS_UNKNOWN)
254     {
255         InitNumaHeapSupport();
256         if (g_ulNumaStatus == NUMA_STATUS_NOT_NUMA)
257         {
258             return GetProcessHeap();
259         }
260     }
261     else if (g_ulNumaStatus == NUMA_STATUS_NOT_NUMA)
262     {
263         /* not NUMA, return the process heap handle */
264         return GetProcessHeap();
265     }
266
267
268     /* return the preferred heap handle from the TLS slot, if set.
269        This is the commonly taken path. */
270
271     hHeap = (HANDLE)TlsGetValue( g_dwTlsHeap );
272
273     if (hHeap != NULL)
274     {
275         return hHeap;
276     }
277
278
279     /* preferred heap handle not yet set.
280        determine the numa node we're executing on, and create a heap which
281        is assigned to this node.
282        one (soft) assumption that is made here is that thread affinity has
283        been set such that threads do not move between NUMA nodes.
284      */
285
286     smalloc_GetCurrentProcessorNumberEx(&CurrentProcessorNumber);
287
288     if (!smalloc_GetNumaProcessorNodeEx(&CurrentProcessorNumber, &CurrentNumaNodeNumber))
289     {
290         /* GetNumaProcessorNodeEx() can fail on WOW64/32bit if invoked
291            against processor numbers > 32.
292            this should never be reached for 64bit builds.
293          */
294         CurrentNumaNodeNumber = 0;
295     }
296
297
298     /* check if the NUMA node array slot already contains a heap */
299     /* CurrentNumaNodeNumber cannot execeed count of heaps, as NUMA nodes
300        cannot be added. */
301
302     hHeap = g_hHeap[ CurrentNumaNodeNumber ];
303
304     if (hHeap == NULL)
305     {
306         HANDLE hHeapPrior = NULL;
307         ULONG  ulOption   = 2; /* HEAP_LFH */
308
309         /* create a heap for this numa node
310            defer creating the heap - while running on each node - to ensure
311            the heap control structures get created on the local NUMA node.
312          */
313
314         hHeap = HeapCreate(0, 0, 0);
315
316         if (hHeap == NULL)
317         {
318             /* just return the process heap.  We'll try to create a heap
319                again next time */
320             return GetProcessHeap();
321         }
322
323         /* make the new heap a low-fragmentation heap */
324
325         HeapSetInformation(
326                 hHeap,
327                 0,          /* HeapCompatibilityInformation */
328                 &ulOption,
329                 sizeof(ulOption)
330                 );
331
332         /* set the array slot entry for this NUMA node to contain the newly
333            allocated heap */
334
335         hHeapPrior = (HANDLE)InterlockedCompareExchangePointer(&(g_hHeap[CurrentNumaNodeNumber]), hHeap, NULL);
336         if (hHeapPrior != NULL)
337         {
338             HeapDestroy( hHeap );
339             hHeap = hHeapPrior;
340         }
341     }
342
343     /* we reached here since there was no heap assigned to the TLS slot.
344        Assign it. */
345     TlsSetValue(g_dwTlsHeap, hHeap);
346
347     return hHeap;
348 }
349
350
351
352
353 void *tMPI_Malloc_local(size_t size)
354 {
355     HANDLE         hHeap;
356     unsigned char *ptr;
357     size_t         new_size;
358     HEAPHEADER    *phdr;
359
360     hHeap = ReturnHeapHandle();
361
362     new_size = size + HEAPHEADER_SIZE;
363
364     ptr = (unsigned char *)HeapAlloc( hHeap, 0, new_size );
365
366     if (ptr == NULL)
367     {
368         return NULL;
369     }
370
371     phdr = (HEAPHEADER*)ptr;
372
373     phdr->dwMagic          = HEAP_NUMA_MAGIC;
374     phdr->hHeap            = hHeap;     /* track the heap handle for realloc
375                                            and free */
376     phdr->cbAllocationSize = new_size;  /* track the allocation size for
377                                            realloc and debugging */
378
379     return ( ptr + HEAPHEADER_SIZE );
380 }
381
382
383 void *tMPI_Calloc_local(size_t nelem, size_t elsize)
384 {
385     void  *ptr;
386     size_t size = nelem * elsize;
387
388     ptr = tMPI_Malloc_local(size);
389
390     if (ptr != NULL)
391     {
392         memset(ptr, 0, size);
393     }
394
395     return ptr;
396 }
397
398
399 void *tMPI_Realloc_local(void *ptr, size_t size)
400 {
401     HANDLE         hHeap;
402     HEAPHEADER    *phdr;
403     unsigned char *new_ptr;
404     size_t         new_size;
405
406     /* calculate the allocation address and check for presence of the hint
407        which indicates this was allocated by our allocator.
408      */
409
410     phdr = (HEAPHEADER*)((unsigned char*)ptr - HEAPHEADER_SIZE);
411
412     if (phdr->dwMagic != HEAP_NUMA_MAGIC)
413     {
414         /* TODO: call tMPI_Error() */
415         /*gmx_fatal(errno,__FILE__,__LINE__,
416            "Invalid Heap header during realloc. %p", ptr);*/
417
418         return NULL;
419     }
420
421     /* calculate size of new/realloc'd block.
422      */
423
424     new_size = size + HEAPHEADER_SIZE;
425
426     /* if the NUMA Node changed between the initial allocation and the
427        reallocation, copy the memory to an allocation on the new local node:
428        we assume the new realloc'd block is more likely to be manipulated by
429        the current thread which is calling realloc.
430        the simple way to detect this condition is to compare the preferred heap
431        handle with the heap handle stored in the current memory block.
432      */
433
434     hHeap = ReturnHeapHandle();
435
436     if (hHeap != phdr->hHeap)
437     {
438         new_ptr = HeapAlloc( hHeap, 0, new_size );
439
440         /* if the new allocation succeeded, copy the buffer and free the
441            original buffer.
442          */
443
444         if (new_ptr != NULL)
445         {
446             size_t copy_size;
447
448             /* the realloc can be larger or smaller than the original
449                allocation size.
450              */
451
452             if (new_size > phdr->cbAllocationSize)
453             {
454                 copy_size = phdr->cbAllocationSize;
455             }
456             else
457             {
458                 copy_size = new_size;
459             }
460
461             /* copy the current memory block contents into the newly allocated
462                buffer, and then free the original buffer.
463              */
464
465             memcpy( new_ptr, phdr, copy_size );
466
467             HeapFree( phdr->hHeap, 0, phdr );
468         }
469
470     }
471     else
472     {
473
474         /* NodeNumber of existing allocation matches current node number.
475            realloc from the heap associated with the existing allocation.
476          */
477
478         hHeap = phdr->hHeap;
479
480         new_ptr = HeapReAlloc(
481                     hHeap,
482                     0,
483                     phdr,
484                     new_size
485                     );
486     }
487
488     if (new_ptr == NULL)
489     {
490         return NULL;
491     }
492
493     phdr                   = (HEAPHEADER*)new_ptr;
494     phdr->cbAllocationSize = new_size;  /* update allocation size to match
495                                            realloc */
496     phdr->hHeap            = hHeap;
497
498     return ( new_ptr + HEAPHEADER_SIZE );
499 }
500
501
502 int tMPI_Free_numa(void *ptr)
503 {
504     HEAPHEADER *phdr;
505
506     /* caller doesn't call us on ptr == NULL case, so we don't need to
507        check that here. */
508
509     phdr = (HEAPHEADER*)((unsigned char*)ptr - HEAPHEADER_SIZE);
510
511     /* this check should happen in __try / __except block until the
512        mis-matched free/sfree calls are fixed, but this is here primarilly
513        for debugging and catching mis-matched memory alloc and free references.
514      */
515
516     if (phdr->dwMagic != HEAP_NUMA_MAGIC)
517     {
518         /* ptr is leaked here, rather than faulting in the allocator.
519            this is done in order to track mis-matched alloc/free calls.
520          */
521         return 1;
522     }
523
524     phdr->dwMagic = 0;
525
526     HeapFree( phdr->hHeap, 0, phdr );
527
528     return 0;
529 }
530
531 #endif /* NUMA allocation functions for (_WIN32 || _WIN64) */