我正在一台拥有4台Operton 6272处理器,运行centOS的机器上试验NUMA.有8个NUMA节点,每个节点有16GB内存.
这是我正在运行的一个小测试程序.
void pin_to_core(size_t core)
{
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(core, &cpuset);
pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset);
}
int main()
{
pin_to_core( 0 );
size_t bufSize = 100;
for( int i = 0; i < 131000; ++i )
{
if( !(i % 10) )
{
std::cout << i << std::endl;
long long free = 0;
for( unsigned j = 0; j < 8; ++j )
{
numa_node_size64( j, &free );
std::cout << "Free on node " << j << ": " << free << std::endl;
}
}
char* buf = (char*)numa_alloc_onnode( bufSize, 5 );
for( unsigned j = 0; j < bufSize; ++j )
buf[j] = j;
}
return 0;
}
因此,基本上在核心#0上运行的线程在NUMA节点5上分配131K 100字节缓冲区,用垃圾初始化它们并泄漏它们.每10次迭代,我们就会打印出每个NUMA节点上可用内存量的信息.
在输出的开头,我得到:
0
Free on node 0: 16115879936
Free on node 1: 16667398144
Free on node 2: 16730402816
Free on node 3: 16529108992
Free on node 4: 16624508928
Free on node 5: 16361529344
Free on node 6: 16747118592
Free on node 7: 16631336960
...
最后我得到了:
Free on node 0: 15826657280
Free on node 1: 16667123712
Free on node 2: 16731033600
Free on node 3: 16529358848
Free on node 4: 16624885760
Free on node 5: 16093630464
Free on node 6: 16747384832
Free on node 7: 16631332864
130970
Free on node 0: 15826657280
Free on node 1: 16667123712
Free on node 2: 16731033600
Free on node 3: 16529358848
Free on node 4: 16624885760
Free on node 5: 16093630464
Free on node 6: 16747384832
Free on node 7: 16631332864
mbind: Cannot allocate memory
mbind: Cannot allocate memory
mbind: Cannot allocate memory
mbind: Cannot allocate memory
mbind: Cannot allocate memory
mbind: Cannot allocate memory
mbind: Cannot allocate memory
130980
...
我不清楚的事情:
1)为什么有那些“mbind:无法分配内存”的消息?事实上,如果我将缓冲区大小更改为1000,那么我远没有用尽所有内存并且行为不会改变,这让我觉得我的某些内核资源句柄已经用完了.
2)即使我要求在节点5上分配内存,实际分配似乎已在节点0和5之间分配.
任何人都可以提供任何有关为什么会发生这种情况的见解?
UPDATE
想提供关于第(2)点的更多细节.部分内存未在节点5上分配的事实似乎与我们正在初始化核心#0(属于NUMA节点0)上的缓冲区这一事实有关.如果我将pin_to_core(0)更改为pin_to_core(8),则在节点1和5之间分配分配的内存.如果是pin_to_core(40),则在节点5上分配所有内存.
UPDATE2
我查看了libnuma的源代码,并尝试使用更多低级调用替换对numa_alloc_onnode()的调用:mmap()和mbind().我现在也在检查内存所在的NUMA节点 – 我使用move_pages()调用.结果如下.在初始化之前(j上的循环),页面没有映射到任何节点(我得到ENOENT错误代码),并且在初始化之后,页面被分配给节点0或节点5.模式是常规的:5,0,5, 0,…和以前一样,当我们接近第131000次迭代时,对mbind()的调用开始返回错误代码,当发生这种情况时,页面总是被分配给节点0.mbind返回的错误代码是ENOMEM ,文档说这意味着用尽“内核内存”.我不知道它是什么,但它不能是“物理”内存,因为我每个节点有16GB.
所以这是我目前的结论:
>当另一个NUMA节点的核心首先触及内存时,mbind()强加的内存映射限制仅占50%.我希望在某处记录这一点,因为悄悄地违背承诺并不好……
>对mbind的调用次数有限制.所以应该尽可能mbind()大内存块.
我要尝试的方法是:在固定到特定NUMA ndo核心的线程上执行内存分配任务.为了更加安心,我将尝试调用mlock(因为描述的问题here).
解决方法:
正如您在阅读libnuma.c时发现的那样,每次调用numa_alloc_onnode()都会创建一个新的匿名内存映射,然后将内存区域绑定到指定的NUMA节点.有了这么多的mmap()调用,你只需要达到每个进程允许的最大内存映射数.该值可以从/ proc / sys / vm / max_map_count读取,也可以由系统管理员通过写入伪文件来修改:
# echo 1048576 > /proc/sys/vm/max_map_count
或者使用sysctl:
# sysctl -w vm.max_map_count=1048576
Linux发行版的默认值为65530映射. mmap()实现映射合并,即它首先尝试在创建新映射之前扩展现有映射.在我的测试中,它在每次调用时创建一个新映射,否则扩展前一个映射.在第一次调用numa_alloc_onnode()之前,我的测试进程有37个映射.因此,在2 *(65530-37)= 130986次调用之后,mmap()应该开始失败.
看起来当mbind()应用于现有映射的一部分时,会发生奇怪的事情并且新受影响的区域未正确绑定.我必须深入研究内核源代码以找出原因.另一方面,如果你更换:
numa_alloc_onnode( bufSize, 5 )
同
numa_alloc_onnode( bufSize, i % 4 )
没有执行映射合并,并且mmap()在第65500次迭代周围失败,并且所有分配都被正确绑定.