Merge release-4-6 into master
[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     } else if( g_ulNumaStatus == NUMA_STATUS_NOT_NUMA )
261     {
262         /* not NUMA, return the process heap handle */
263         return GetProcessHeap();
264     }
265
266
267     /* return the preferred heap handle from the TLS slot, if set.  
268        This is the commonly taken path. */
269
270     hHeap = (HANDLE)TlsGetValue( g_dwTlsHeap );
271
272     if( hHeap != NULL )
273     {
274         return hHeap;
275     }
276
277
278     /* preferred heap handle not yet set.
279        determine the numa node we're executing on, and create a heap which 
280        is assigned to this node.
281        one (soft) assumption that is made here is that thread affinity has 
282        been set such that threads do not move between NUMA nodes.
283     */
284
285     smalloc_GetCurrentProcessorNumberEx(&CurrentProcessorNumber);
286         
287     if(!smalloc_GetNumaProcessorNodeEx(&CurrentProcessorNumber, &CurrentNumaNodeNumber))
288     {
289         /* GetNumaProcessorNodeEx() can fail on WOW64/32bit if invoked 
290            against processor numbers > 32.
291            this should never be reached for 64bit builds.
292         */
293         CurrentNumaNodeNumber = 0;
294     }
295
296
297     /* check if the NUMA node array slot already contains a heap */
298     /* CurrentNumaNodeNumber cannot execeed count of heaps, as NUMA nodes 
299        cannot be added. */
300
301     hHeap = g_hHeap[ CurrentNumaNodeNumber ];
302
303     if( hHeap == NULL )
304     {
305         HANDLE hHeapPrior = NULL;
306         ULONG ulOption = 2; /* HEAP_LFH */
307
308         /* create a heap for this numa node
309            defer creating the heap - while running on each node - to ensure 
310            the heap control structures get created on the local NUMA node.
311         */
312
313         hHeap = HeapCreate(0, 0, 0);
314     
315         if( hHeap == NULL )
316         {
317             /* just return the process heap.  We'll try to create a heap 
318                again next time */
319             return GetProcessHeap();
320         }
321
322         /* make the new heap a low-fragmentation heap */
323
324         HeapSetInformation(
325                 hHeap,
326                 0,          /* HeapCompatibilityInformation */
327                 &ulOption,
328                 sizeof(ulOption)
329                 );
330
331         /* set the array slot entry for this NUMA node to contain the newly 
332            allocated heap */
333
334         hHeapPrior = (HANDLE)InterlockedCompareExchangePointer(&(g_hHeap[CurrentNumaNodeNumber]), hHeap, NULL);
335         if( hHeapPrior != NULL )
336         {
337             HeapDestroy( hHeap );
338             hHeap = hHeapPrior;
339         }
340     }
341
342     /* we reached here since there was no heap assigned to the TLS slot.  
343        Assign it. */
344     TlsSetValue(g_dwTlsHeap, hHeap);
345
346     return hHeap;
347 }
348
349
350
351
352 void *tMPI_Malloc_local(size_t size)
353 {
354     HANDLE hHeap;
355     unsigned char *ptr;
356     size_t new_size;    
357     HEAPHEADER *phdr;
358
359     hHeap = ReturnHeapHandle();
360
361     new_size = size + HEAPHEADER_SIZE;
362
363     ptr = (unsigned char *)HeapAlloc( hHeap, 0, new_size );
364
365     if( ptr == NULL )
366     {
367         return NULL;
368     }
369
370     phdr = (HEAPHEADER*)ptr;
371
372     phdr->dwMagic = HEAP_NUMA_MAGIC;
373     phdr->hHeap = hHeap;                /* track the heap handle for realloc 
374                                            and free */
375     phdr->cbAllocationSize = new_size;  /* track the allocation size for 
376                                            realloc and debugging */
377
378     return ( ptr + HEAPHEADER_SIZE );
379 }
380
381
382 void *tMPI_Calloc_local(size_t nelem, size_t elsize)
383 {
384     void *ptr;
385     size_t size = nelem * elsize;
386
387     ptr = tMPI_Malloc_local(size);
388
389     if(ptr != NULL)
390     {
391         memset(ptr, 0, size);
392     }
393
394     return ptr;
395 }
396
397
398 void *tMPI_Realloc_local(void *ptr, size_t size)
399 {
400     HANDLE hHeap;
401     HEAPHEADER *phdr;
402     unsigned char *new_ptr;
403     size_t new_size;
404
405     /* calculate the allocation address and check for presence of the hint 
406        which indicates this was allocated by our allocator.
407     */
408
409     phdr = (HEAPHEADER*)((unsigned char*)ptr - HEAPHEADER_SIZE);
410
411     if( phdr->dwMagic != HEAP_NUMA_MAGIC )
412     {
413                 /* TODO: call tMPI_Error() */ 
414         /*gmx_fatal(errno,__FILE__,__LINE__,
415           "Invalid Heap header during realloc. %p", ptr);*/
416
417         return NULL;
418     }
419
420     /* calculate size of new/realloc'd block.
421     */
422
423     new_size = size + HEAPHEADER_SIZE;
424
425     /* if the NUMA Node changed between the initial allocation and the 
426        reallocation, copy the memory to an allocation on the new local node:
427        we assume the new realloc'd block is more likely to be manipulated by 
428        the current thread which is calling realloc.
429        the simple way to detect this condition is to compare the preferred heap 
430        handle with the heap handle stored in the current memory block.
431     */
432
433     hHeap = ReturnHeapHandle();
434
435     if( hHeap != phdr->hHeap )
436     {
437         new_ptr = HeapAlloc( hHeap, 0, new_size );
438
439         /* if the new allocation succeeded, copy the buffer and free the 
440            original buffer.
441         */
442
443         if( new_ptr != NULL )
444         {
445             size_t copy_size;
446
447             /* the realloc can be larger or smaller than the original 
448                allocation size.
449             */
450
451             if( new_size > phdr->cbAllocationSize )
452             {
453                 copy_size = phdr->cbAllocationSize;
454             } else {
455                 copy_size = new_size;
456             }
457
458             /* copy the current memory block contents into the newly allocated 
459                buffer, and then free the original buffer.
460             */
461
462             memcpy( new_ptr, phdr, copy_size );
463
464             HeapFree( phdr->hHeap, 0, phdr );
465         }
466     
467     } else {
468
469         /* NodeNumber of existing allocation matches current node number.
470            realloc from the heap associated with the existing allocation.
471         */
472
473         hHeap = phdr->hHeap;
474
475         new_ptr = HeapReAlloc(
476                     hHeap,
477                     0,
478                     phdr,
479                     new_size
480                     );
481     }
482
483     if( new_ptr == NULL )
484     {
485         return NULL;
486     }
487
488     phdr = (HEAPHEADER*)new_ptr;
489     phdr->cbAllocationSize = new_size;  /* update allocation size to match 
490                                            realloc */
491     phdr->hHeap = hHeap;
492
493     return ( new_ptr + HEAPHEADER_SIZE );
494 }
495
496
497 int tMPI_Free_numa(void *ptr)
498 {
499     HEAPHEADER *phdr;
500
501     /* caller doesn't call us on ptr == NULL case, so we don't need to 
502        check that here. */
503
504     phdr = (HEAPHEADER*)((unsigned char*)ptr - HEAPHEADER_SIZE);
505
506     /* this check should happen in __try / __except block until the 
507        mis-matched free/sfree calls are fixed, but this is here primarilly
508        for debugging and catching mis-matched memory alloc and free references.
509     */
510
511     if(phdr->dwMagic != HEAP_NUMA_MAGIC)
512     {
513         /* ptr is leaked here, rather than faulting in the allocator.
514            this is done in order to track mis-matched alloc/free calls.
515         */
516         return 1;
517     }
518
519     phdr->dwMagic = 0;
520
521     HeapFree( phdr->hHeap, 0, phdr );
522
523     return 0;
524 }
525
526 #endif /* NUMA allocation functions for (_WIN32 || _WIN64) */
527
528
529
530