深入理解 Linux 核心--jemalloc 引起的 TLB shootdown 及優化
本文選自 “位元組跳動基礎架構實踐” 系列文章。
“位元組跳動基礎架構實踐” 系列文章是由位元組跳動基礎架構部門各技術團隊及專家傾力打造的技術乾貨內容,和大家分享團隊在基礎架構發展和演進過程中的實踐經驗與教訓,與各位技術同學一起交流成長。
“延遲突刺”、“效能抖動” 等問題通常會受到多方因素影響不便排查,本文以線上問題為例,詳解 TLB shootdown,最終使得 CPU 的消耗降低 2% 左右,並消除了抖動突刺,變得更加穩定。
問題背景
在網際網路業務執行的過程中,難免遇到 “延遲突刺”、“效能抖動” 等問題,而通常這類問題會受到多種軟體環境甚至硬體環境的影響,原因較為隱晦,解決起來相對棘手。
本文以一個線上問題為例子,深入 x86 體系結構,結合核心記憶體管理的知識,輔以多種 Linux 平臺上的 Debug 工具,詳解 TLB shootdown 問題,最終解決掉該問題,提升了業務效能。
名詞約定
Kernel: 本文中特指 Linux-4.14。
KVM: Kernel-based Virtual Machine。現在主流的虛擬機器技術之一。
Host: 指虛擬化場景下的宿主機。
Guest: 指虛擬化場景下的虛擬機器。
APIC: Advanced Programmable Interrupt Controller 。Intel CPU 使用的中斷控制器。
LAPIC: Local Advanced Programmable Interrupt Controller 。
IPI: Inter-Processor Interrupt。CPU 之間相互通知使用。
MMU: Memory Management Unit。Kernel 用來管理虛擬地址和實體地址對映的硬體。
TLB: Translation Lookaside Buffer。MMU 為了加速查詢頁表,使用的 cache。用來加速 MMU 的轉化速度。
PTE: Page Table Entry 。管理頁表使用的頁表項。
jemalloc: 一個使用者態記憶體管理的庫,在多執行緒併發的場景下,malloc/free 的效能好於 glibc 預設的實現。
TLB shootdown
TLB shootdown 是如何發生的
如上圖所示,一個程式有 4 個 thread 並行執行。由於 4 個 thread 共享同一個程式的頁表,在執行的過程中,通過把 pgd 載入到 cr3 的方式, 每個 CPU 的 TLB 中載入了相同的 page table 。
如果 CPU0 上,想要修改 page table,尤其是想要釋放一些記憶體,那麼需要修改 page table, 同時修改自己的 TLB (或者重新載入 TLB)。
然而,這還不夠。例如,CPU0 上釋放了 page A,並且 page A 被 kernel 回收,很有可能被其他的程式使用。但是, CPU1、CPU2 以及 CPU3 的 TLB 中還是快取了對應的 PTE 表項,依然可以訪問到 page A 。
為了防止這個事情發生, CPU0 需要通知 CPU1、CPU2 和 CPU3,也需要在 TLB 中禁用掉對應的 PTE 。通知的方式就是使用 IPI (Inter-Processor Interrupt)。
在虛擬化的場景下,IPI 的成本比較高。如果 Guest 中有大量的 IPI,就會看到 Guest 的 CPU sys 暴漲。同時,在 Host 上可以發現虛擬機器發生 vmexit 突增,其中主要是 wrmsr 的 ICR Request 產生。(熟悉 x86 的同學知道,x2apic 模式下,x86 上 IPI 的實現即通過 wrmsr 指令請求 ICR)
如何確認是 TLB shootdown 引起的問題
- 在 Guest 中執行:
#watch -d -n 1 "cat /proc/interrupts | grep TLB"
複製程式碼
如果看到資料上漲比較厲害,那麼基本就可以看到問題了。
- 在 Guest 中執行:
#perf top
複製程式碼
如果看到 smp_call_function_many ,那麼很不幸,就是在批量傳送 IPI。
好訊息是這個場景並不常見,比較特定的情況下才會發生。典型的就是使用者態程式中呼叫了系統呼叫:
int madvise(void *addr, size_t length, MADV_DONTNEED);
複製程式碼
- 如何檢查程式使用了 jemalloc,jemalloc 會呼叫 madvise,見下文:
# ls /proc/*/maps | xargs grep jemalloc
複製程式碼
- 確實對應的程式是否在執行 madvise 的方法, MADV_DONTNEED 會釋放頁表項,進而引起 tlb shootdown。所以 MADV_DONTNEED 是判斷 tlb 問題的重要線索:
# strace -f -p 1510 2>&1 | grep madvise
複製程式碼
madvise MADV_DONTNEED 和 munmap
確認上述的 TLB shootdown 問題之後,我們再來回顧一下,系統呼叫 madvise 到底起了什麼作用呢?
int madvise(void *addr, size_t length, MADV_DONTNEED);
複製程式碼
記憶體分配的一般過程
- 使用 mmap 分配一段虛擬地址空間;
- 第一次訪問到某一個 4k 內的地址的時候,MMU 發現沒有對應的 PTE,觸發 page fault;
- kernel 分配對應的 page。
如果使用了 DONTNEED,就會釋放對應的 page。如果下一次再訪問到,就會重複上述的 2 和 3。
效果就是短暫的 page 歸還 kernel 之後,下次訪問重新分配。
madvise DONTNEED 和 munmap 的區別
例如 state 0 所示,使用者態程式分配了 VMA0 和 VMA1 兩個虛擬機器地址空間。有的地址上已經分配了物理頁面(例如 0x800000),有的還沒有分配(例如 0x802000)。
如 state 1 所示,使用者態程式第一次訪問到了例如 0x802000 地址的時候,觸發了 page fault ,核心為使用者態程式的 0x802000 分配了物理頁面(地址是 0x202000)。
如 state 2 所示,執行了:
madvise(0x800000, 8192, MADV_DONTNEED)
複製程式碼
之後,核心釋放了對應的物理頁面。那麼下一次訪問到 0x800000 ~ 0x801fff 的時候,就會觸發 page fault。處理過程類似 state 1。
如 state 3 所示,執行了:
munmap(0x800000, 16384);
複製程式碼
就把對應的 VMA 釋放了。那麼下次訪問到 0x800000 ~ 0x803fff 的時候,就會觸發 segment fault 。因為地址已經釋放,屬於非法地址,核心會給程式傳送 signal 11 。大部分情況下,會殺掉程式。
使用 jemalloc ENV 解決 TLB shootdown
問題產生自 jemalloc,所以嘗試從 jemalloc 本身入手解決問題。
嘗試去社群,問 jemalloc 的 maintainer,是否有辦法解決 TLB shootdown 引起的問題,maintainer 建議通過 jemalloc 環境變數(MALLOC_CONF)動態控制 jemalloc 是否啟動 madvise。問題和答覆見:
https://github.com/jemalloc/jemalloc/issues/1422
複製程式碼
在本地寫測試程式碼,實際測試 jemalloc(比較靠近 upstream 的 5.0 版本)和 maintainer 給出來的建議,在程式啟動前匯入環境變數:
MALLOC_CONF=dirty_decay_ms:-1,muzzy_decay_ms:-1
複製程式碼
可以驗證可以成功避免問題。該環境變數可以解決 tlb 問題,詳細引數作用請參看手冊:
http://jemalloc.net/jemalloc.3.html#opt.dirty_decay_ms
複製程式碼
某業務同樣使用了 jemalloc,但是測試沒有效果。
對業務實際使用的 so 動態連結庫進行:
#strings libjemalloc.so.2 | grep -i version
複製程式碼
可以發現實際使用的版本是:
JEMALLOC_VERSION "4.2.0-0-gf70a254d44c8d30af2cd5d30531fb18fdabaae6d"
複製程式碼
通過閱讀 jemalloc 的原始碼發現,在 4.2 版本的時候,還不支援 maintainer 給出來的變數引數。但是可以通過如下變數來達到類似的效果:
MALLOC_CONF=purge:decay,decay_time:-1
複製程式碼
設定了 jemalloc 的引數之後,業務的表現得到了明顯的提升。如下圖所示,最後一個零點和前一個零點進行對比,CPU 的抖動情況得到了很大的改善,從之前的 6% 左右抖動到低於 4% 的穩定執行,且 CPU 的消耗曲線更加穩定平滑。
與此同時,業務上的延遲也更加穩定,PCT99 也降低了延遲突刺情況。
寫在最後
在解決問題的過程中,也並非如文章所寫的一般有序進行。期間也多次使用 perf 觀察熱點函式的變化;使用 atop 對比前後的業務表現和系統指標;也觀察虛擬化的監控資料(wrmsr 的數量)等等手段,一步一步排除干擾,鎖定問題。
隨著當代作業系統的複雜度的提高,問題的難度也在提高。在解決問題的過程中,我們也在進步!
最後,歡迎加入位元組跳動基礎架構團隊,一起探討、解決問題,一起變強!
更多分享
位元組跳動基礎架構團隊
位元組跳動基礎架構團隊是支撐位元組跳動旗下包括抖音、今日頭條、西瓜視訊、火山小視訊在內的多款億級規模使用者產品平穩執行的重要團隊,為位元組跳動及旗下業務的快速穩定發展提供了保證和推動力。
公司內,基礎架構團隊主要負責位元組跳動私有云建設,管理數以萬計伺服器規模的叢集,負責數萬臺計算/儲存混合部署和線上/離線混合部署,支援若干 EB 海量資料的穩定儲存。
文化上,團隊積極擁抱開源和創新的軟硬體架構。我們長期招聘基礎架構方向的同學,具體可參見job.bytedance.com,感興趣可以聯絡郵箱 arch-graph@bytedance.com 。
歡迎關注位元組跳動技術團隊
- 加微信實戰群請加微信(註明:實戰群):gocnio
相關文章
- 深入理解JVM(③)Java的鎖優化JVMJava優化
- 深入理解flutter的編譯原理與優化Flutter編譯原理優化
- linux核心引數優化重要項Linux優化
- 深入理解Kafka核心設計及原理(二):生產者Kafka
- 深入理解Kafka核心設計及原理(四):主題管理Kafka
- 深入理解Kafka核心設計及原理(三):消費者Kafka
- Linux核心調優Linux
- Apache ShardingSphere 5.0.0 核心優化及升級指南Apache優化
- Linux中Vsftpd服務的部署及優化LinuxFTP優化
- 深入理解redis的持久化Redis持久化
- Web 效能優化:理解及使用 JavaScript 快取Web優化JavaScript快取
- jQuery的基本理解及核心函式與核心物件jQuery函式物件
- 深入學習 Linux 核心模組Linux
- Java核心(五)深入理解BIO、NIO、AIOJavaAI
- 深入理解Vue元件3大核心概念Vue元件
- 深入理解JVM效能調優JVM
- Linux命令補充及基礎優化。Linux優化
- 【譯】Web 效能優化:理解及使用 JavaScript 快取Web優化JavaScript快取
- 深入理解Linux許可權Linux
- jvm優化理解JVM優化
- 深入理解 Docker 核心原理:Namespace、Cgroups 和 RootfsDockernamespace
- MySQL 核心深度優化MySql優化
- 深入理解JVM(③)Java的模組化JVMJava
- linux shell陣列深入學習理解Linux陣列
- 由Linux核心bug引起SSH登入緩慢問題的排查與解決Linux
- Java核心(二)深入理解執行緒池ThreadPoolJava執行緒thread
- 深入理解SpringBoot核心機制《spring-boot-starter》Spring Boot
- 深入理解 Java 序列化Java
- 理解索引:索引優化索引優化
- 深入理解 CSS:基礎概念、註釋、選擇器及優先順序CSS
- 深入理解 python 虛擬機器:破解核心魔法——反序列化 pyc 檔案Python虛擬機
- 編譯Redis時報錯: jemalloc/jemalloc.h: No such file or directory編譯Redis
- 深入理解RPC框架的序列化方案RPC框架
- 深入理解Java虛擬機器(程式編譯與程式碼優化)Java虛擬機編譯優化
- 深入理解多執行緒(五)—— Java虛擬機器的鎖優化技術執行緒Java虛擬機優化
- linux常用核心最佳化Linux
- 深入理解Java ClassLoader及在 JavaAgent 中的應用Java
- 購買Javascript核心原理解析 優惠碼JavaScript