Faiss的多线程效率问题

过去的一周,被AI组的同事拉去分析faiss的多核心效率低下的问题。Faiss是由Facebook主导开源的一个“向量搜索(召回)算法”库。简单的说:Faiss能在一大堆库存的向量中找出与指定向量最相似的几向量。

从理论上看,作为一个明显的CPU计算型的应用,更多的core意味着更大的计算吞吐能力。性能只会越来越好才是。可现实是当通过taskset命令分配更多的core给faiss(在本文中亦特指一个以faiss为基础的ivf proxy benchmark工具)只会带来更长响应时间以及更大的响应时间偏差(variation)。

更多core带来更长响应时间以及更大的响应时间偏差

一开始,我首先检查了CPU利用率,发现即便是性能最差的情况,CPU都是满载,这就非常不合情理了。本以为可能性最大(也是最容易解决)的一条路被堵死了,只能认栽,从调黑盒性能变成了调白盒性能。

代码是根据faiss自带的tutorial/cpp/3-IVFPQ.cpp改进而来。测试的流程大致上可以划分为3个阶段:建库(train)、校验(sanity check)、搜索。把每一个环节的耗时统计出来,发现校验过程并不需要太多时间,可以直接排除掉。而剩余的两个阶段都存在“核心越多,性能越差”的奇怪表现。而考虑到具体线上的正常使用模式:建库的过程是一次性的。意味着对建库的修复过程没有这么紧要,那就直接去到搜索阶段的优化。

基于break down。搜索阶段又分两个部分,一个是quantizer,一个是search_preassigned。这么说把,search_preassigned又是耗时大头。对这个函数经过1000次测试之后发现了一个问题:导致单个core和多个core之间性能差异的并非是大多数情况,是少数outlier(离群,囧)导致的平均数增加。outlier的出现频率和偏离成都与核心数呈正比关系。

outlier的出现频率和偏离成都与核心数呈正比关系

这种状况就算是诡异了,从分部特征来看,这应该跟线程的计算有关。在AI组的同事指导下将nprobe值和batch设置强置为1,从算法上保证search_preassigned只能用单核单线程。本来希望满满的,结果还是照旧。并且奇怪的是即便已经确信只有单线程在执行,系统中照样还是在多个核心上满负载。

search_preassigned函数单线程模式

看来白盒不是我的风格了,切换回黑盒方法,直接用perf top命令查看系统调用栈。发现在多核、单线程的模式下占比最高的居然是libgomp,而真正的benchmark(6-IVFPQ)只占了很少的CPU资源。

这就是核心的问题了:

  1. Faiss的多核心是通过openMP实现的。
  2. 默认OMP_NUM_THREADS等于所有可用的CPU数,即OpenMP默认将会在启动与核心数相同的线程数作为线程池。
  3. 默认情况下,openmp假定所有的调用都是计算密集型的。为了减少线程启动/唤醒过程需要上下文开销,系统必须时刻保证每一个线程都是alive状态。换句话说,要让线程活着,OpenMP会让线程池的每个线程做大量的无意义计算占据时间片而不是wait挂起。
  4. quantizer 的过程中系统启动了omp线程池,理论上在修改后的search_preassigned开始后,线程池已经没有任何意义。但在放任不管的情况下,系统的每个核心的CPU使用率都会被空白计算占据,理论上100%。主线程结束之前线程池不会自己销毁。
  5. 这个时候如果位于主线程上的search_preassigned函数需要执行,那就不得不与OMP线程池抢占CPU time。这就是核心越多性能越差的原因。而放大这个影响的原因是我们的测试程序经过了变态级别的优化之后导致OMP的线程维护开销远远大于任务的CPU开销(微秒级响应,少于0.1个最小上下文)。这个测试事实上成为了某种程度“系统调度时延”测量。这个结果恰恰反应了预期。

剩下的就是解这个问题了,那只要在合适的时候让线程池销毁所有线程就迎刃而解了。OpenMP的实现是基于编译器的,并没有详细的代码可以参考。找了文档,发现没有办法可以直接实现我的目的,只有两个对应的环境变量可以缓解:

  • GOMP_SPINCOUNT=<int n> omp线程经过了n个spin lock之后便被挂起。自然,n值越小就越早的挂起线程。
  • OMP_WAIT_POLICY=PASSIVE 通过使用wait方法挂起 omp线程。对应的ACTIVE 意味着线程池中的线程始终处于活动状态——消耗大量的CPU。

我这里就用了后者,展示下优化结果,同样的坐标系之下,效果还是很感人的。再次perf top,omp线程已经不再出现在头部了。

推荐阅读:
CPU各级缓存

被问起CPU的各级缓存,才想起

PMU Event counter的使用状况检测

题目用中文反而有点绕,How

AMD Rome benchmark数据到架构特征推导

这几天,拿到了一套最新的AMD

一个测试性能不稳定的问题

经常会通过一些通用的测试工具测

发表评论

电子邮件地址不会被公开。 必填项已用*标注

请补全下列算式: *

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据