likes
comments
collection
share

浅谈NUMA架构[三]-NUMA下如何优化CPU

作者站长头像
站长
· 阅读数 18

在摩尔定律失效的情况下,CPU开始通过多核来提高性能,但这带来了一系列的问题,本系列的文章将介绍这些问题、在X86下目前的解决方案、以及我们应该如何更好的使用CPU。

建议收藏,本文将持续更新~ 前文: 浅谈NUMA架构[一]-CPU架构演进过程 - 掘金 (juejin.cn) 浅谈NUMA架构[二]-NUMA下CPU访问内存整体流程 - 掘金 (juejin.cn)

一、CPU使用优化

如何评估是否系统瓶颈在CPU?

首先需要观察CPU使用情况,对于CPU idle、Top命令的CPU%、perf的ipc(Instruction per cycle),这三者得到的数据一致吗?

浅谈NUMA架构[三]-NUMA下如何优化CPU

Top和idle的指标会使用CPU处于非idle状态下的时间 / 整体时间 计算CPU使用百分比,这在一定程度上能反映CPU的状态,但是却不能给CPU调优提供更多的信息了;

对于perf的ipc指标,会考虑到CPU处于等待数据从Memory加载到Cache的耗时(CPU Install),一般情况下(cpu wide = 4),对于多核系统:

  • IPC < 1,当前系统偏向处于Memory Bound,此时即使再增加CPU的效果对于系统性能的提升也是有限的;
  • IPC > 1,当前系统偏向处于CPU Instructions Bound,即CPU处在相对较高的运行负荷上。

NUMA下如何优化

Memory Bound

对于Memory Bound,在内存带宽一定的情况下优先考虑:

  • 优化代码设计,减少不必要的Memory IO;常见的方式比如:数据库的列式存储,对于Count()、Max()等列式聚合查询,相比于行式存储需要将全部完整行数据从db->mem->cache,而列式只需要将完整列进行load即可,对于内存带宽的压力减少非常多,而CPU处在Memory Stall的时间自然也会降低。
  • 优化Cache的命中率,因为L2相比于Memory可以节省约230cycle;常见的比如:代码Cache友好:缓存行填充(Disruptor),不过这种方式比较hardcode;另一种常见的方式比如:在一个CacheLine中可以存储数组中的多个值时,数组按行遍历相比于按列遍历是Cache友好的,在数据量和访问量较大时,性能会有50%以上的提升;相比于缓存行有更新原因不同的多种数据导致的Cache回写,在NUMA下更常见的是因为线程切核导致的Cache命中率降低,因此可以通过taskset或者通过Java框架Affinity修改线程对核的亲和性来避免线程迁移核心执行,不过绑核的前提是需要认知不同CPU任务对于CPU资源的消耗程度,尽量使每个核上绑定的线程执行的任务量相近;

CPU Bound

对于CPU Bound,在CPU核心数、clock rate不变的情况下优先考虑:

  • 使得一个CPU cycle可以处理更多的内存数据,常见的方式比如SIMD(Single Instruction Multiple Data),在Java9中对于String.indexOf()方法已经开始尝试使用SIMD进行字符串查找,相比于传统的for循环单字符匹配,性能要好上不少。
  • 优化代码设计,减少不必要的CPU运算,在业务系统中常见的比如Json全量序列化在只取部分字段时,是否可以改为流式的获取方式;比如将经常重复计算的计算结果放到映射表中去;
  • 优化线程模型,减少不必要的线程数 ,CFS调度器会保证每一个就绪线程在一次调度周期中会得到调度,线程越多,线程切换越多,每个调度周期便越长,维护成本也越高。

二、主要参考