AMD服务器,多线程应用绑定核心,选择不同的CPU核心,性能差距可达50%。最近有幸为项目拿到了一台AMDEPYC系列的测试服务器,发现了一些奇怪的现象。本次测试服务器采用双AMDEPYC7552处理器,属于二代罗马(Zen2)架构,单通道48个物理核心,双通道共192个逻辑核心(线程),两个NUMA节点。为了测试,事先写了一个简单的多线程程序:两个线程,即producer和consumer,模拟route-worker模型;三个线程,分别是producer、forwarder和consumer,模拟管道模型。线程使用无锁队列通信。生产者依次写入1~100000000,消费者取出数字求和。线程每次写入或读取队列数据时,都会执行一些无意义的循环来消耗时间和模拟业务逻辑。所有线程分别绑定核心,避免线程迁移导致的缓存抖动,绑定的核心属于同一个CPU。所有队列都分配在该CPU的本地内存上,避免了跨NUMA的远程内存访问。奇怪现象测试发现线程绑定不同的core,性能有明显差异:Corebinding说明:Cores#4#5#6#8#12#100都是同一个CPU,没有交叉-NUMA内存访问情况;Core#4#100是一对SMT核心,即从同一个物理核心虚拟出来的两个逻辑核心;黄色条中涉及的#48核心属于其他CPU,存在跨NUMA内存访问情况,仅供对比。测试结果反映了一个很奇怪的现象:线程绑定核心,在同一个NUMA中选择不同的核心,性能差距达到50%(route-workermodel#4#5vs#4#8)甚至140%(管道模型#4#5#6与#4#8#12)。为什么是这样?复杂的内存层次结构模型从内存层次结构开始。一般按照延迟时间从小到大,内存级别可以分为:(1)L1,一级缓存;(2)L2,二级缓存;(3)L3,也叫LLC,三级缓存;(4)记忆力。在具体实现上,传统的英特尔至强系列模型比较简单:每个物理核心虚拟出两个逻辑核心(TR1/TR2,TR3/TR4),每个物理核心有自己的L1和L2,所有物理核心共享L3。解释了一些高性能程序开发的优化策略:Avoidcross-NUMAremotememoryaccess。除了降低访问延迟,对L3也更加友好。给核心绑定线程,避免Cachejitter,具体是避免L1和L2jitter共享L3的存在,是透明的,软件不关心,也不能关心AMD的架构改变了这一切。AMD在2017年发布了Zen架构,其中一个重要的设计原则就是:一个CPU由多个CCX(CPUComplex)堆叠而成。那么,什么是CCX?简单来说,CCX其实就是4个物理核心(8个逻辑核心)+L3。CCX通过IF总线与IODie(Rome)相连,实现CCX之间的互通,实现内存和IO的通信。图片来源:https://frankdenneman.nl/2019/10/14/amd-epyc-naples-vs-rome-and-vsphere-cpu-scheduler-updates/因此,AMDEPYC的内存模型与传统模型大不同:L3不是所有物理核共享,而是同一个CCX中的4个物理核共享。类似于NUMA引入的“remotememory”概念,CCX引入了“remoteL3”的概念。在网上找了一张访问延迟表供参考:结论和优化建议结论是在AMD服务器下,要想获得更高的性能,需要针对L3进行优化。方法是:将一组任务(线程、进程)绑定到同一个CCX下的核心。那么我们怎么知道哪些核心是同一个CCX呢?可以使用hwloc-ls命令:#0#96#1#97#2#98#3#99是4个物理核心和8个逻辑核心。共享16MB的L3,因此这些内核属于同一个CCX。所以绑定核心的时候可以绑定#0#1#2#3#96#97#98#99,或者#4#5#6#7#100#101#102#103,等等。文章开头的测试结果很好解释:#4#5#6都是同一个CCX,因为共享L3,每个读写队列其实都是读写L3,所以性能高;#4#8#12分属于3个不同的CCX,每次写入queue时,其他CCX的L3数据都会失效,所以读queue时,必须从内存中读取,所以性能较差。最后可以通过:perfstat-er510143,r510243,r510843,r511043,r514043./xxx查看L3、PMC的访问状态AMD官方文档的代码:可以看到boundcore的内存读取次数#4#8几乎是并列核心#4#53次。
