CFS 頻寬控制¶
注意
本文件僅討論 SCHED_NORMAL 的 CPU 頻寬控制。 SCHED_RT 的情況在 即時組排程 中介紹。
CFS 頻寬控制是一個 CONFIG_FAIR_GROUP_SCHED 擴充套件,允許指定組或層次結構可用的最大 CPU 頻寬。
一個組允許的頻寬使用配額和週期來指定。 在每個給定的“週期”(微秒)內,任務組最多分配“配額”微秒的 CPU 時間。 該配額以切片的形式分配給每個 CPU 的執行佇列,因為 cgroup 中的執行緒變得可執行。 一旦分配了所有配額,任何額外的配額請求都將導致這些執行緒被限制。 受限制的執行緒將無法再次執行,直到下一個週期,配額被補充。
組的未分配配額在全域性範圍內進行跟蹤,並在每個週期邊界重新整理回 cfs_quota 單位。 當執行緒消耗此頻寬時,它會按需傳輸到 CPU 本地“silos”。 每次更新中傳輸的量是可調的,並描述為“slice”。
突發特性¶
此功能現在借用時間來對抗我們未來的欠執行,代價是增加了對其他系統使用者的干擾。 所有這些都被很好地限制了。
傳統的 (UP-EDF) 頻寬控制類似於
(U = Sum u_i) <= 1
這保證了每個截止日期都能滿足並且系統是穩定的。 畢竟,如果 U > 1,那麼對於每一秒的實際時間,我們都必須執行超過一秒的程式時間,並且顯然錯過了我們的截止日期,但是下一個截止日期會更遠,永遠沒有時間趕上,無限制的失敗。
突發特性觀察到工作負載並不總是執行完整的配額; 這使得人們可以將 u_i 描述為統計分佈。
例如,假設 u_i = {x,e}_i,其中 x 是 p(95) 並且 x+e 是 p(100)(傳統的 WCET)。 這有效地允許 u 更小,從而提高效率(我們可以將更多工打包到系統中),但代價是當所有機率都一致時會錯過截止日期。 但是,它確實保持了穩定性,因為只要我們的 x 高於平均值,每個超限都必須與一個欠限配對。
也就是說,假設我們有 2 個任務,都指定了一個 p(95) 值,那麼我們有 p(95)*p(95) = 90.25% 的機率兩個任務都在他們的配額之內,一切都很好。 與此同時,我們有 p(5)p(5) = 0.25% 的機率兩個任務將同時超過他們的配額(保證截止日期失敗)。 在兩者之間的某個地方,存在一個閾值,其中一個超過而另一個的欠限不足以補償; 這取決於具體的 CDF。
與此同時,我們可以說最壞情況下的截止日期錯過將是 Sum e_i; 也就是說,存在有界的遲延(假設 x+e 確實是 WCET)。
使用突發時的干擾由錯過截止日期的可能性和平均 WCET 來評估。 測試結果表明,當有許多 cgroup 或 CPU 利用率不足時,干擾是有限的。 更多詳細資訊請參見:https://lore.kernel.org/lkml/5371BD36-55AE-4F71-B9D7-B86DC32E3D2B@linux.alibaba.com/
管理¶
配額、週期和突發透過 cgroupfs 在 cpu 子系統中進行管理。
注意
本節中描述的 cgroupfs 檔案僅適用於 cgroup v1。 對於 cgroup v2,請參見 Documentation/admin-guide/cgroup-v2.rst。
cpu.cfs_quota_us:在週期內補充的執行時間(以微秒為單位)
cpu.cfs_period_us:週期的長度(以微秒為單位)
cpu.stat:匯出限制統計資訊 [在下面進一步解釋]
cpu.cfs_burst_us:最大累積執行時間(以微秒為單位)
預設值為
cpu.cfs_period_us=100ms
cpu.cfs_quota_us=-1
cpu.cfs_burst_us=0
cpu.cfs_quota_us 的值 -1 表示該組沒有任何頻寬限制,這樣的組被描述為非約束頻寬組。 這代表了 CFS 的傳統工作保留行為。
寫入任何(有效的)正值,不小於 cpu.cfs_burst_us,將實施指定的頻寬限制。 允許的最小配額或週期為 1 毫秒。 週期長度也有上限為 1 秒。 當頻寬限制以分層方式使用時,存在其他限制,這些限制將在下面更詳細地解釋。
將任何負值寫入 cpu.cfs_quota_us 將刪除頻寬限制,並將該組再次返回到非約束狀態。
cpu.cfs_burst_us 的值為 0 表示該組無法累積任何未使用的頻寬。 它使 CFS 的傳統頻寬控制行為保持不變。 將任何(有效的)正值(不大於 cpu.cfs_quota_us)寫入 cpu.cfs_burst_us 將對未使用的頻寬累積實施上限。
對組頻寬規範的任何更新都會導致該組在受限狀態下解除限制。
系統範圍設定¶
為了提高效率,執行時間在全域性池和 CPU 本地“silos”之間批次傳輸。 這大大減輕了大型系統上的全域性帳戶壓力。 每次需要此類更新時傳輸的量被描述為“slice”。
這可以透過 procfs 進行調整
/proc/sys/kernel/sched_cfs_bandwidth_slice_us (default=5ms)
較大的切片值將減少傳輸開銷,而較小的值允許更細粒度的消耗。
統計資訊¶
組的頻寬統計資訊透過 cpu.stat 中的 5 個欄位匯出。
cpu.stat
nr_periods:已經經過的強制間隔數。
nr_throttled:該組已被限制/限制的次數。
throttled_time:該組的實體已被限制的總時間持續時間(以納秒為單位)。
nr_bursts:發生突發的週期數。
burst_time:任何 CPU 在各自週期內使用的超過配額的累積實際時間(以納秒為單位)。
此介面是隻讀的。
分層注意事項¶
該介面強制執行單個實體的頻寬始終是可實現的,即:max(c_i) <= C。但是,顯式允許聚合情況下的過度訂閱,以在層次結構中啟用工作保留語義
例如,Sum (c_i) 可能超過 C
[ 其中 C 是父級的頻寬,c_i 是其子級 ]
組可能被限制有兩種方式
它在週期內完全消耗了自己的配額
父級的配額在其週期內被完全消耗
在上面的情況 b) 中,即使子級可能剩餘執行時間,它也不會被允許執行,直到父級的執行時間被重新整理。
CFS 頻寬配額注意事項¶
一旦將 slice 分配給 CPU,它就不會過期。 但是,如果該 CPU 上的所有執行緒都變為不可執行,則除了 1 毫秒之外的所有 slice 都可以返回到全域性池。 這是在編譯時由 min_cfs_rq_runtime 變數配置的。 這是一個性能調整,有助於防止全域性鎖上增加的爭用。
CPU 本地 slice 不會過期這一事實導致了一些有趣的極端情況,應該理解。
對於 CPU 限制的 cgroup CPU 約束應用程式,這是一個相對無關緊要的點,因為它們自然會消耗其配額的全部以及每個週期中每個 CPU 本地 slice 的全部。 因此,預計 nr_periods 大致等於 nr_throttled,並且 cpuacct.usage 在每個週期中大約等於 cfs_quota_us。
對於高度執行緒化的,非 CPU 繫結的應用程式,這種非過期細微差別允許應用程式在每個任務組正在執行的每個 CPU 上(通常最多 1 毫秒/CPU 或由 min_cfs_rq_runtime 定義)短暫地超過其配額限制。 這種輕微的突發僅在配額已分配給 CPU 並且先前週期中未完全使用或返回時適用。 此突發量不會在核心之間傳輸。 因此,此機制仍然嚴格地將任務組限制為配額平均使用量,儘管時間視窗比單個週期更長。 這也限制了突發能力不超過每個 CPU 1 毫秒。 這為在高核心數計算機上具有小配額限制的高度執行緒化應用程式提供了更好,更可預測的使用者體驗。 它還消除了限制這些應用程式的傾向,同時使用小於配額量的 CPU。 另一種說法是,透過允許 slice 的未使用部分在週期內保持有效,我們減少了浪費地使 CPU 本地 silos 上不需要完整 slice CPU 時間的配額過期的可能性。
還應考慮 CPU 繫結和非 CPU 繫結互動式應用程式之間的互動,尤其是在單核使用率達到 100% 時。 如果您為每個應用程式提供 CPU 核心的一半,並且它們都在同一個 CPU 上進行排程,則理論上非 CPU 繫結應用程式在某些週期內最多可以使用 1 毫秒的額外配額,從而阻止 CPU 繫結應用程式透過相同的量完全使用其配額。 在這些情況下,將由 CFS 演算法(請參見 CFS 排程器)來決定選擇哪個應用程式執行,因為它們都將是可執行的並且剩餘配額。 這種執行時差異將在互動式應用程式空閒的以下週期中得到彌補。
示例¶
將一個組限制為 1 個 CPU 的執行時間
If period is 250ms and quota is also 250ms, the group will get 1 CPU worth of runtime every 250ms. # echo 250000 > cpu.cfs_quota_us /* quota = 250ms */ # echo 250000 > cpu.cfs_period_us /* period = 250ms */
在多 CPU 計算機上將一個組限制為 2 個 CPU 的執行時間
透過 500 毫秒的週期和 1000 毫秒的配額,該組每 500 毫秒可以獲得 2 個 CPU 的執行時間
# echo 1000000 > cpu.cfs_quota_us /* quota = 1000ms */ # echo 500000 > cpu.cfs_period_us /* period = 500ms */ The larger period here allows for increased burst capacity.
將一個組限制為 1 個 CPU 的 20%。
使用 50 毫秒的週期,10 毫秒的配額相當於 1 個 CPU 的 20%
# echo 10000 > cpu.cfs_quota_us /* quota = 10ms */ # echo 50000 > cpu.cfs_period_us /* period = 50ms */
透過在此處使用小週期,我們以突發容量為代價確保了一致的延遲響應。
將一個組限制為 1 個 CPU 的 40%,並允許額外累積高達 1 個 CPU 的 20%,以防已完成累積。
使用 50 毫秒的週期,20 毫秒的配額相當於 1 個 CPU 的 40%。 10 毫秒的突發相當於 1 個 CPU 的 20%
# echo 20000 > cpu.cfs_quota_us /* quota = 20ms */ # echo 50000 > cpu.cfs_period_us /* period = 50ms */ # echo 10000 > cpu.cfs_burst_us /* burst = 10ms */
更大的緩衝區設定(不大於配額)允許更大的突發容量。