字節跳動在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 對二進制進行優化,實現基於採樣數據的軟件預取優化等等,使字節跳動業務從中獲得更大的受益。