位元組跳動在PGO反饋優化技術上的探索與實踐

語言: CN / TW / HK

歡迎關注【位元組跳動 SYS Tech】公眾號。位元組跳動 SYS Tech 聚焦系統技術領域,與大家分享前沿技術動態、技術創新與實踐、行業技術熱點分析等內容。

背景

隨著位元組跳動的業務迅速增長,微服務的效能優化工作顯得尤為重要,對於頭部應用來說,提升若干百分點的效能也能為公司節省巨大的伺服器資源成本。

編譯器優化是軟體效能優化的一種常用方法,相比其它特定的效能優化方法,它的適用性更廣,能更全面地獲得性能收益,從而降低成本。編譯反饋優化(PGO)是常見的編譯器的優化方法,位元組跳動 STE 團隊在編譯反饋優化技術方向進行了持續的探索,並在位元組跳動業務上積極地開展了實踐工作,成功將編譯反饋優化技術大規模落地於位元組跳動業務,為公司節省了大量資源。

PGO簡介

PGO(Profile-guided optimization)通常也叫做 FDO(Feedback-directed optimization),它是一種編譯優化技術,它的原理是編譯器使用程式的執行時 profiling 資訊,生成更高質量的程式碼,從而提高程式的效能。

傳統的編譯器優化通常藉助於程式的靜態分析結果以及啟發式規則實現,而在被提供了執行時的 profiling 資訊後,編譯器可以對應用進行更好的優化。通常來說編譯反饋優化能獲得 10%-15% 的效能收益,對於特定特徵的應用(例如使用編譯反饋優化 Clang本身)收益高達 30%。

編譯反饋優化通常包括以下手段:

  1. Inlining,例如函式 A 頻繁呼叫函式 B,B 函式相對小,則編譯器會根據計算得出的 threshold 和 cost 選擇是否將函式 B inline 到函式 A 中。
  2. ICP(Indirect call promotion),如果間接呼叫(Call Register)非常頻繁地呼叫同一個被呼叫函式,則編譯器會插入針對目標地址的比較和跳轉指令。使得該被呼叫函式後續有了 inlining 和更多被優化機會,同時增加了 icache 的命中率,減少了分支預測的失敗率。
  3. Register allocation,編譯器能使用執行時資料做更好的暫存器分配。
  4. Basic block optimization,編譯器能根據基本塊的執行次數進行優化,將頻繁執行的基本塊放置在接近的位置,從而優化 data locality,減少訪存開銷。
  5. Size/speed optimization,編譯器根據函式的執行時資訊,對頻繁執行的函式選擇效能高於程式碼密度的優化策略。
  6. Function layout,類似於 Basic block optimization,編譯器根據 Caller/Callee 的資訊,將更容易在一條執行路徑上的函式放在相同的段中。
  7. Condition branch optimization,編譯器根據跳轉資訊,將更容易執行的分支放在比較指令之後,增加icache 命中率。
  8. Memory intrinsics,編譯器根據 intrinsics 的呼叫頻率選擇是否將其展開,也能根據 intrinsics 接收的引數優化 memcpy 等 intrinsics 的實現。

編譯器需要 profiling 資訊對應用進行優化,profile 的獲取通常有兩種方式:

  • Instrumentation-based(基於插樁)
  • Sample-based(基於取樣)

Instrumentation

Instrumentation-based PGO 的流程分為三步驟:

  • 編譯器對程式原始碼插樁編譯,生成插樁後的程式(instrumented program)。
  • 執行插樁後的程式,生成 profile 檔案。
  • 編譯器使用 profile 檔案,再次對原始碼進行編譯。

圖片

Instrumentation-based PGO 對程式碼插樁包括:

1. 插入計數器(counter)

  • 對編譯器 IR 計算 MST,計算頻繁跳轉的邊,對不在 MST 上的邊插入計數器,用於減少插樁程式碼對執行時效能的影響。
  • 在函式入口插入計數器。

2. 插入探針(probes)

  • 收集間接函式呼叫地址(indirect call addresses)。

  • 收集部分函式的引數值。

Sampling

Sample-based PGO 的流程同樣分為三步驟:

  • 編譯器對程式原始碼進行編譯,生成帶除錯資訊的程式(program with debug information)。
  • 執行帶除錯資訊的程式,使用 profiler(例如linux perf)採集執行時的效能資料。
  • 編譯器使用 profile 檔案,再次對原始碼進行編譯。

圖片

其中步驟2採集的資料為二進位制級別取樣資料(例如 linux perf 使用 perf record 命令收集得到 perf.data 檔案)。二進位制取樣資料通常包含的是程式的 PC 值,我們需要使用工具,讀取被取樣程式的除錯資訊(例如使用 AutoFDO 等工具),將程式的原始二進位制取樣資料生成程式原始碼行號對應的取樣資料,提供給編譯器使用。

探索與落地

對比 sampled-based PGO,Instrumentation-based PGO 的優點採集的效能資料較為準確,但繁瑣的流程使其在位元組跳動業務上難以大規模落地,主要原因有以下幾點:

  • 應用二進位制編譯時間長,引入的額外編譯流程影響了開發、版本釋出的效率。
  • 產品迭代速度快,程式碼更新頻繁,熱點資訊與應用瓶頸變化快。而 instrumented-based PGO 無法使用舊版本收集的 profile 資料編譯新版本,需要頻繁地使用插樁後的最新版本收集效能資料。
  • 插樁引入了額外的效能開銷,這些效能開銷會影響業務應用的效能特徵,收集的 profile 不能準確地表示正常版本的效能特徵,從而降低優化的效果,使得 instrumented-based PGO 的優點不再明顯。

使用 Sample-based PGO 方案可以有效地解決以上問題:

  • 無需引入額外的編譯流程,為程式新增額外的除錯資訊不會明顯地降低編譯效率。
  • Sample-based PGO 對過時的 profile 有一定的容忍性,不會對優化效果產生較大影響。
  • 取樣引入的額外效能開銷很小,可以忽略不計,不會對業務應用的效能特徵造成影響。

我們的目標是在位元組跳動業務上大規模落地 PGO 技術,使位元組跳動大量的應用能夠從 PGO 技術中受益。PGO 本身是一項較為成熟的編譯器優化技術,我們面臨的主要問題為如何簡化 PGO 繁瑣的流程,自動化資料採集與優化流程,保證優化的可靠性。我們設計了系統解決這些問題,該系統的架構如圖所示:圖片其中包含的主要任務有:

  • 叢集維度的業務效能資料採集、維護與處理。

  • 業務二進位制與除錯資訊維護。

  • 取樣資料的查詢與使用。

  • 生成編譯器可用的資訊(LLVM profile)。

  • Profile 更新,業務效能測試與發版上線。

資料採集與處理

我們做了大量的前期工作,在 SPEC CPU2017 等 benchmark 上進行了 PGO 測試與結果分析,基於測試結果得出結論,包括:不同取樣事件以及 LBR 資料對 Profile 準確度的影響,單機取樣頻率與額外效能開銷之間的聯絡,叢集取樣資料聚合策略以及樣本數量與優化效果的聯絡等。

我們在位元組跳動伺服器叢集的物理機例項中部署了取樣程式,該程式在後臺以使用者無感知的方式進行著效能資料的常態化採集工作,並將採集的效能資料上傳。原始資料被解析處理後,最終維護在取樣資料庫中。

對於每一條取樣記錄,我們會維護它的原始資訊,以及額外的元資料,這些資訊包括:

  • 取樣地址

  • 取樣的 LBR(last branch record)資料

  • 取樣事件型別

  • 取樣所屬二進位制的 ID

  • 取樣業務的 PSM

  • 其它元資料

收集二進位制與符號

我們搭建了二進位制倉庫,維護著所有線上執行的二進位制以及其描述資訊。對於加入到二進位制倉庫的二進位制,系統會觸發任務對其進行處理,提取 PGO 任務所需的符號資訊,維護在指定的資料庫中。

對於符號資訊的查詢,系統會返回指定二進位制的 PC 值對應的 inline stack 資訊,供 PGO 任務使用。

使用PGO優化流程

我們搭建了 PGO 平臺,供業務使用者使用 PGO 優化流程。業務接入 PGO 優化流程,首先需要通過平臺指定策略。策略的作用是:

  • 指定目標業務的二進位制以及使用場景等資訊。
  • 指定生成 profile 需要的樣本量以及時間視窗等資訊。
  • 建立定時任務,為指定業務生成適用於 LLVM 的 PGO profile。定時任務會定時向取樣資料庫傳送請求,查詢目標二進位制某一時間視窗內的效能資料,查詢結果會被序列化。隨後任務會解析取樣資料以及相應的符號資訊,生成 profile 檔案,並將產物上傳到指定儲存中,供業務編譯時使用。

Profile 檔案會被策略定期更新,業務開發同學在構建時通過選項控制使用 profile 編譯得到 PGO 優化後的二進位制版本,進行常規的版本釋出流程,效能對比測試,決定是否上線優化版本。

落地成果

我們在位元組跳動內部頭部 30+ 應用上落地了 PGO 優化流程,業務均獲得明顯的 CPU 以及延遲收益,平均獲得了超過 5% 的 CPU 收益。
圖片

總結

PGO 技術大規模在位元組跳動業務落地實踐後取得了顯著收益,在其過程中,STE 團隊也克服了諸多技術難題:完成了叢集維度的資料採集工作,保證了資料的準確性同時做到了對業務無感知;在業務快速迭代的現狀下,完成了二進位制倉庫的建設,規範了二進位制符號資訊的查詢流程;搭建了 PGO 優化平臺,簡化了流程,業務方在構建程式時只需新增指定編譯選項,大幅降低業務使用 PGO 的成本;在業務程式組成複雜依賴眾多的現狀下,分析預編譯庫的熱點資訊以及 PGO 效果不及預期的原因,推進了原始碼依賴的編譯流程;同一個程式有可能被多個業務使用,在不同使用場景、時間段下,程式特徵會有明顯區別,我們在系統設計層面,有效地解決了該問題。

STE 團隊會在後續的工作中完善已有系統,並對 PGO 技術進行持續的探索。包括支援 PGO 與 LTO 對業務的共同優化,在 PGO 平臺引入 post-link time optimizer 對二進位制進行優化,實現基於取樣資料的軟體預取優化等等,使位元組跳動業務從中獲得更大的受益。