深入理解 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 是如何發生的


深入理解 Linux 內核--jemalloc 引起的 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)。


深入理解 Linux 內核--jemalloc 引起的 TLB shootdown 及優化


在虛擬化的場景下,IPI 的成本比較高。如果 Guest 中有大量的 IPI,就會看到 Guest 的 CPU sys 暴漲。同時,在 Host 上可以發現虛擬機發生 vmexit 突增,其中主要是 wrmsr 的 ICR Request 產生。(熟悉 x86 的同學知道,x2apic 模式下,x86 上 IPI 的實現即通過 wrmsr 指令請求 ICR)

如何確認是 TLB shootdown 引起的問題

  1. 在 Guest 中執行:
<code>#watch -d -n 1 "cat /proc/interrupts | grep TLB"
/<code>

如果看到數據上漲比較厲害,那麼基本就可以看到問題了。

  1. 在 Guest 中執行:
<code>#perf top
/<code>

如果看到 smp_call_function_many,那麼很不幸,就是在批量發送 IPI。

好消息是這個場景並不常見,比較特定的情況下才會發生。典型的就是用戶態進程中調用了系統調用:

<code>int madvise(void *addr, size_t length, MADV_DONTNEED);
/<code>
  1. 如何檢查進程使用了 jemalloc,jemalloc 會調用 madvise,見下文:
<code># ls /proc/*/maps | xargs grep jemalloc
/<code>


深入理解 Linux 內核--jemalloc 引起的 TLB shootdown 及優化


  1. 確實對應的進程是否在執行 madvise 的方法, MADV_DONTNEED 會釋放頁表項,進而引起 tlb shootdown。所以 MADV_DONTNEED 是判斷 tlb 問題的重要線索:
<code># strace -f -p 1510 2>&1 | grep madvise
/<code>


深入理解 Linux 內核--jemalloc 引起的 TLB shootdown 及優化


madvise MADV_DONTNEED 和 munmap

確認上述的 TLB shootdown 問題之後,我們再來回顧一下,系統調用 madvise 到底起了什麼作用呢?

<code>int madvise(void *addr, size_t length, MADV_DONTNEED);
/<code>

內存分配的一般過程

  1. 使用 mmap 分配一段虛擬地址空間;
  2. 第一次訪問到某一個 4k 內的地址的時候,MMU 發現沒有對應的 PTE,觸發 page fault;
  3. kernel 分配對應的 page。

如果使用了 DONTNEED,就會釋放對應的 page。如果下一次再訪問到,就會重複上述的 2 和 3。

效果就是短暫的 page 歸還 kernel 之後,下次訪問重新分配。

madvise DONTNEED 和 munmap 的區別


深入理解 Linux 內核--jemalloc 引起的 TLB shootdown 及優化


例如 state 0 所示,用戶態進程分配了 VMA0 和 VMA1 兩個虛擬機地址空間。有的地址上已經分配了物理頁面(例如 0x800000),有的還沒有分配(例如 0x802000)。

如 state 1 所示,用戶態進程第一次訪問到了例如 0x802000 地址的時候,觸發了 page fault,內核為用戶態進程的 0x802000 分配了物理頁面(地址是 0x202000)。

如 state 2 所示,執行了:

<code>madvise(0x800000, 8192, MADV_DONTNEED)
/<code>

之後,內核釋放了對應的物理頁面。那麼下一次訪問到 0x800000 ~ 0x801fff 的時候,就會觸發 page fault。處理過程類似 state 1。

如 state 3 所示,執行了:

<code>munmap(0x800000, 16384);
/<code>

就把對應的 VMA 釋放了。那麼下次訪問到 0x800000 ~ 0x803fff 的時候,就會觸發 segment fault。因為地址已經釋放,屬於非法地址,內核會給進程發送 signal 11。大部分情況下,會殺掉進程。

使用 jemalloc ENV 解決 TLB shootdown

問題產生自 jemalloc,所以嘗試從 jemalloc 本身入手解決問題。

嘗試去社區,問 jemalloc 的 maintainer,是否有辦法解決 TLB shootdown 引起的問題,maintainer 建議通過 jemalloc 環境變量(MALLOC_CONF)動態控制 jemalloc 是否啟動 madvise。問題和答覆見:

<code>https://github.com/jemalloc/jemalloc/issues/1422
/<code>

在本地寫測試代碼,實際測試 jemalloc(比較靠近 upstream 的 5.0版本)和 maintainer 給出來的建議,在進程啟動前導入環境變量:

<code>MALLOC_CONF=dirty_decay_ms:-1,muzzy_decay_ms:-1
/<code>

可以驗證可以成功避免問題。該環境變量可以解決 tlb 問題,詳細參數作用請參看手冊:

<code>http://jemalloc.net/jemalloc.3.html#opt.dirty_decay_ms
/<code>

某業務同樣使用了 jemalloc,但是測試沒有效果。

對業務實際使用的 so 動態鏈接庫進行:

<code>#strings libjemalloc.so.2 | grep -i version
/<code>

可以發現實際使用的版本是:

<code>JEMALLOC_VERSION "4.2.0-0-gf70a254d44c8d30af2cd5d30531fb18fdabaae6d"
/<code>

通過閱讀 jemalloc 的源代碼發現,在 4.2 版本的時候,還不支持 maintainer 給出來的變量參數。但是可以通過如下變量來達到類似的效果:

<code>MALLOC_CONF=purge:decay,decay_time:-1
/<code>

設置了 jemalloc 的參數之後,業務的表現得到了明顯的提升。如下圖所示,最後一個零點和前一個零點進行對比,CPU 的抖動情況得到了很大的改善,從之前的 6% 左右抖動到低於 4% 的穩定運行,且 CPU 的消耗曲線更加穩定平滑。

與此同時,業務上的延遲也更加穩定,PCT99 也降低了延遲突刺情況。


深入理解 Linux 內核--jemalloc 引起的 TLB shootdown 及優化


寫在最後

在解決問題的過程中,也並非如文章所寫的一般有序進行。期間也多次使用 perf 觀察熱點函數的變化;使用 atop 對比前後的業務表現和系統指標;也觀察虛擬化的監控數據(wrmsr 的數量)等等手段,一步一步排除干擾,鎖定問題。

隨著當代操作系統的複雜度的提高,問題的難度也在提高。在解決問題的過程中,我們也在進步!

最後,歡迎加入字節跳動基礎架構團隊,一起探討、解決問題,一起變強!

更多分享

字節跳動自研萬億級圖數據庫 & 圖計算實踐

字節跳動 EB 級 HDFS 實踐


字節跳動基礎架構團隊

字節跳動基礎架構團隊是支撐字節跳動旗下包括抖音、今日頭條、西瓜視頻、火山小視頻在內的多款億級規模用戶產品平穩運行的重要團隊,為字節跳動及旗下業務的快速穩定發展提供了保證和推動力。

公司內,基礎架構團隊主要負責字節跳動私有云建設,管理數以萬計服務器規模的集群,負責數萬臺計算/存儲混合部署和在線/離線混合部署,支持若干 EB 海量數據的穩定存儲。

文化上,團隊積極擁抱開源和創新的軟硬件架構。我們長期招聘基礎架構方向的同學,具體可參見 https://job.bytedance.com/ ,感興趣可以聯繫郵箱 [email protected]


分享到:


相關文章: