Faiss使用多執行緒出現的效能問題

DWVictor發表於2021-08-31

Faiss使用多執行緒出現的效能問題

faiss在增加CPU的情況下,反而出現效率低下的問題。

從理論上看,作為一個CPU/GPU計算型的應用,更多的核意味著更大的計算吞吐能力,效能只會越來越好才是。

在實際過程中,通過taskset命令分配更多的核給faiss只會帶來更長響應時間以及更大的響應時間偏差(variation)。

faiss的主要流程:建庫(train)、校驗(sanity check)、搜尋。

由於建庫是一次性操作,就不考慮建庫帶來的影響。對校驗分析,也沒有發現需要耗時太多的時間,那麼主要問題就在搜尋上面。

對於”核心越多,效能越差”的奇怪表現,搜尋階段又分兩個部分,一個是quantizer,一個是search_preassigned。考慮應該跟執行緒的計算有關,於是將nprobe值和batch設定強置為1,從演算法上保證search_preassigned只能用單核單執行緒。結果發現多核cpu依然滿載。

那麼直接用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= omp執行緒經過了n個spin lock之後便被掛起。自然,n值越小就越早的掛起執行緒。
  • OMP_WAIT_POLICY=PASSIVE 通過使用wait方法掛起 omp執行緒。對應的ACTIVE 意味著執行緒池中的執行緒始終處於活動狀態——消耗大量的CPU。

看上去效果還是不錯的。

PS:並沒有對所有情況進行測試,現在的結果指向是由於faiss依賴的openblat庫中額外的omp執行緒池出現了問題導致主執行緒效能受到影響。理論上多執行緒也會如此,只是概率上導致看上去效能不是線性下降。

然後OMP_WAIT_POLICY的問題,主要是對在openmp下使用OpenBLAS的BLAS實現的時候起作用,此外,如果用了MKL庫,一定條件下的MKL同樣也會觸發。

然後在faiss建立索引(train),也可能會出現這種情況,使用最新的faiss1.6.5及以後建立索引,CPU佔用率明顯有下降。

參考:http://www.litrin.net/2020/03/26/faiss的多執行緒效率問題/

https://www.cnblogs.com/yhzhou/p/10568728.html

https://www.cnblogs.com/yangyangcv/archive/2012/03/23/2413335.html

相關文章