背景介紹
性能測試是 SDK 發版的重要依據,VolcRTC 的業務方對于性能指標都比較重視,對于 RTC 準入有明確的準入標準。因此我們建立了線下的性能自動化測試系統,測試過程中我們發現 VolcRTC 的內存占用較高存在較大的優化空間。某個版本 1v1 語音通話 VolcRTC 1v1 語音通話內存占用:
占用的資源 | Memory[MB] |
Android 高端機 | 17.87 |
Android 中端機 | 17.58 |
Android 低端機 | 16.06 |
iOS 高端機 | 6.19 |
iOS 中端機 | 6.52 |
iOS 低端機 | 5.73 |
為了實現內存優化,首先需要理清兩個問題:
- 哪些模塊消耗多少內存?
- 如何優化?
內存組成
在回答以上兩個問題之前,我們先了解下內存的主要組成部分有哪些。
在 Android 系統上,內存主要分為:
下圖紅框部分為 VolcRTC 通話過程
- Java Heap,從 Java 代碼分配的對象;通話過程中 Java 內存的分布曲線,主要呈鋸齒狀的周期性變化。結合 VolcRTC 的業務特點,可以知道這部分內存主要在 JNI 調用時分配臨時對象,累計到一定程度后由系統的 GC 機制回收。
- Native Heap,從 C 或 C++ 代碼分配的對象。這部分為 VolcRTC 主要內存占用。
- Code,用于處理代碼和資源(如 dex 字節碼、經過優化或編譯的 dex 代碼、.so 庫和字體)的內存。VolcRTC 庫所占用內存,但不等于動態庫的包大小,主要原因在于代碼段是按需分頁加載的,所以部分代碼不會被加載到內存。VolcRTC 是一個動態庫,因此 Code 的內存也是在通話過程中主要部分。
優化方向
根據上文的初步分析,可以確定 VolcRTC 的內存占用主要分布在 Native Heap 與 Code 段。因此我們明確大體的優化方向為:
- Native 內存優化
- 動態庫包體優化
內存歸因分析
哪些模塊如何消耗多少內存?
- 內存分配堆棧信息
- 按模塊歸因
Heapprofd 實現原理
- hook malloc、calloc、realloc、free 等內存分配相關的函數
- 拷貝寄存器與棧內存,存儲到共享內存,用于棧回溯
- 根據堆棧信息聚類生成 Trace 文件
模塊歸因
VolcRTC 歸因規則
VolcRTC 主要分為底層媒體引擎與上層 RTC SDK 兩部分。媒體引擎的整體架構是以流水線(Pipeline)的形式組成的,每個 Pipeline 由實現不同功能的 Node 構成。我們可以根據相關的命名空間進行堆棧過濾,再根據軟件分層架構進行層層歸因。
純系統堆棧
VolcRTC 引起的系統堆棧內存分配,堆棧不包含 VolcRTC 符號信息,無法按前述規則歸類,需要歸類到由 VolcRTC 引起的系統內存分配。
歸因示例
內存分配堆棧特征一般為棧底為??__pthread_start(void*)?
?,棧頂為內存存分配方法,中間為 VolcRTC 堆棧信息。根據堆棧信息,結合歸因規則然后層層向上歸因,形成一個樹狀的結構,準確分析每一個 Pipeline、每一個 Node、每一個類型的對象所占用的內存大小。
碰到的問題
Hook malloc 得到的內存大小與 Native Heap 大小不一致
malloc 向內存分配器申請的內存,跟程序運行時傳入的 size 一致。
內存分配器向操作系統申請的內存按頁分配,一般每頁為 4K,Native Heap 統計的是這部分的內存大小。
由于內存分配器的需要反復分配與釋放內存,不可避免的產生內存空隙也就是內存碎片,另外內存分配器會緩存一部分小內存塊以提升內存分配效率。
語音通話內存分析
通過性能自動化測試工具,生成分析報告。基于分析報告我們繪制語音通話內存全景圖,再通過全景圖識別出內存占用較高的幾個模塊,指引優化方向。
內存優化
編譯優化
包大小會直接影響到內存大小,因此優化包大小也可以有效減少內存大小。通過打開 LTO、Oz 等編譯選項,結合線下性能自動化測試評估是否對性能指標有負面影響來決定需要開啟的編譯優化選項。編譯優化后 Android 端動態庫包體減少了 900KB,通話過程內存優化 850KB 左右。
按需動態分配
VolcRTC 作為一個通用功能的 SDK,對于每個特定場景會有很多冗余邏輯與功能,這些邏輯與功能都存在較多的預分配內存。對應的優化方案是:
- 合理代碼組件化,將不同的功能抽象成組件做到靈活組裝與按需加載,如:AI 降噪功能內置的數據和模型會占用較大的內存空間。
- 內存盡量按需動態分配。如:AEC 回聲消除在不同場景下有不同的算法,需要根據實際的場景按需分配內存,減少過多的內存預分配。
設置合理的緩存大小
不合理的緩存大小也會引起不必要的內存浪費。通過內存的歸因分析,結合不同場景的業務特性,設置更加合理的緩存大小,可以減少內存占用。例如:RTC 采用了 RTP 包重傳機制來對抗網絡丟包,為了實現重傳機制,需要緩存一定數量的包,緩存的數量需要跟進幀長、實時性要求等業務特性來設置合理的值。
合理的算法和數據結構設計
合理的算法和數據結構也可以有效降低內存。在保證計算準確性的前提下,通過減少數據值域范圍,使用內存空間占用更小的數據類型來實現算法,比如統計與時間相關的數據時使用相對時間而非絕對時間、空間音頻算法通過定點化使用??short?
?類型代替浮點型數據。另外數據結構設計時需要考慮內存對齊問題。
優化效果
1v1 語音通話
占用的資源 | 優化前 Memory[MB] | 優化后 Memory[MB] |
Android 高端機 | 17.87 | 13.59 |
Android 中端機 | 17.58 | 13.98 |
Android 低端機 | 16.06 | 12.93 |
iOS 高端機 | 6.19 | 3.87 |
iOS 中端機 | 6.52 | 3.84 |
iOS 低端機 | 5.73 | 3.14 |
本次內存優化,我們探索了 RTC 場景下性能歸因分析驅動性能優化的實踐。可以總結出以下經驗:
- 構造穩定的測試用例
- 建立性能折損的數據歸因模型
- 基于歸因模型識別熱點性能問題,形成優化方案
- 從 1v1 通話開始分析,然后逐步到多人、百人千人。