NUMA感知cachingalignment的内存分配

在linux系统中,pthreads库为我们提供了一个用于cachingalignment的函数(posix_memalign)来防止错误的共享。 为了select一个特定的架构NUMA节点,我们可以使用libnuma库。 我想要的是需要两个。 我将某些线程绑定到某些处理器,并且我想为相应的NUMA节点中的每个线程分配本地数据结构,以减less线程的内存操作延迟。 我怎样才能做到这一点?

Solutions Collecting From Web of "NUMA感知cachingalignment的内存分配"

如果您只是希望获得NUMA分配器的对齐功能,则可以轻松构建自己的对齐功能。

这个想法是调用未对齐的malloc()多一点空间。 然后返回第一个对齐的地址。 为了能够释放它,你需要将基地址存储在已知的位置。

这是一个例子。 只需用适当的名称替换名称:

 pint // An unsigned integer that is large enough to store a pointer. NUMA_malloc // The NUMA malloc function NUMA_free // The NUMA free function void* my_NUMA_malloc(size_t bytes,size_t align, /* NUMA parameters */ ){ // The NUMA malloc function void *ptr = numa_malloc( (size_t)(bytes + align + sizeof(pint)), /* NUMA parameters */ ); if (ptr == NULL) return NULL; // Get aligned return address pint *ret = (pint*)((((pint)ptr + sizeof(pint)) & ~(pint)(align - 1)) + align); // Save the free pointer ret[-1] = (pint)ptr; return ret; } void my_NUMA_free(void *ptr){ if (ptr == NULL) return; // Get the free pointer ptr = (void*)(((pint*)ptr)[-1]); // The NUMA free function numa_free(ptr); } 

当你使用这个时,你需要调用my_NUMA_free分配my_NUMA_malloc分配的任何东西。

libnuma中的numa_alloc _ *()函数分配整个内存页,通常为4096字节。 高速缓存行通常是64个字节。 由于4096是64的倍数,因此从numa_alloc _ *()返回的任何内容都将在缓存级别进行memaligned。

请注意numa_alloc _ *()函数。 它在手册页上说,它们比相应的malloc()慢,我相信这是真的,但是我发现的更大的问题是,同时在numa_alloc _ *()上同时运行多个内核的分配遭受重大争议问题。 在我的情况下用numa_alloc_onnode()替换malloc()是一个洗(我通过使用本地内存获得的一切被增加的分配/空闲时间抵消); tcmalloc比任何一个都快。 我一次在32个线程/内核上执行数千个12-16kb的malloc。 时序实验表明,它不是numa_alloc_onnode()的单线程速度,它负责我的进程花费在执行分配上的大量时间,而锁定/争用问题可能是由于可能的原因造成的。 我采用的解决方案是numa_alloc_onnode()大块内存,然后根据需要将其分发到每个节点上运行的线程。 我使用gcc atomic builtins来允许每个线程(我把线程连接到cpus)从每个节点上分配的内存中抓取。 如果你愿意的话,你可以缓存行大小对齐分布。 这种方法甚至超过了tcmalloc的裤子(这是线程感知,但不是NUMA意识 – 至少Debain挤压版本似乎不是)。 这种方法的缺点是你不能释放个人发行版(好吧,不是没有更多的工作),你只能释放整个底层的节点分配。 但是,如果这是一个函数调用的临时节点临时空间,或者您可以另外指定不再需要该内存的时间,则此方法运行良好。 如果你可以预测你需要在每个节点上分配多少内存的话,这也是很有帮助的。

@nandu:我不会发布完整的源代码 – 这是很长的地方绑在我做的其他事情,这使得它不完全透明。 我将发布的是我的新malloc()函数的一个稍微缩短的版本来说明核心思想:

 void *my_malloc(struct node_memory *nm,int node,long size) { long off,obytes; // round up size to the nearest cache line size // (optional, though some rounding is essential to avoid misalignment problems) if ((obytes = (size % CACHE_LINE_SIZE)) > 0) size += CACHE_LINE_SIZE - obytes; // atomically increase the offset for the requested node by size if (((off = __sync_fetch_and_add(&(nm->off[node]),size)) + size) > nm->bytes) { fprintf(stderr,"Out of allocated memory on node %d\n",node); return(NULL); } else return((void *) (nm->ptr[node] + off)); } 

其中struct node_memory是

 struct node_memory { long bytes; // the number of bytes of memory allocated on each node char **ptr; // ptr array of ptrs to the base of the memory on each node long *off; // array of offsets from those bases (in bytes) int nptrs; // the size of the ptr[] and off[] arrays }; 

并且使用libnuma函数numa_alloc_onnode()来设置nm-> ptr [node]。

我通常也在结构中存储了可允许的节点信息,所以my_malloc()可以在不进行函数调用的情况下检查节点请求是否合理。 我也检查nm是否存在,这个大小是明智的。 函数__sync_fetch_and_add()是一个gcc内置的原子函数; 如果你不用gcc编译,你需要别的东西。 我使用原子,因为在我有限的经验中,它们比高线程/内核数条件下的互斥要快得多(就像在4P NUMA机器上一样)。