英語

容量感知排程

1. CPU 容量

1.1 介紹

傳統的同構 SMP 平臺由完全相同的 CPU 組成。而異構平臺則由具有不同效能特徵的 CPU 組成 - 在這樣的平臺上,並非所有 CPU 都可以被認為是相同的。

CPU 容量是衡量 CPU 可以達到的效能的指標,相對於系統中效能最高的 CPU 進行了標準化。 異構系統也稱為非對稱 CPU 容量系統,因為它們包含不同容量的 CPU。

最大可實現效能(IOW,即最大 CPU 容量)的差異源於兩個因素

  • 並非所有 CPU 都可能具有相同的微架構 (µarch)。

  • 使用動態電壓和頻率調整 (DVFS),並非所有 CPU 都可以物理上達到更高的工作效能點 (OPP)。

Arm big.LITTLE 系統是這兩者的一個例子。 大 CPU 比 LITTLE CPU 更注重效能(更多流水線階段、更大的快取、更智慧的預測器等),並且通常可以達到比 LITTLE CPU 更高的 OPP。

CPU 效能通常以每秒百萬指令 (MIPS) 表示,也可以表示為每個 Hz 可達到的給定數量的指令,從而得出

capacity(cpu) = work_per_hz(cpu) * max_freq(cpu)

1.2 排程器術語

排程器中使用兩個不同的容量值。 CPU 的 原始容量 是其最大可達到的容量,即其最大可達到的效能水平。 此原始容量由函式 arch_scale_cpu_capacity() 返回。 CPU 的 容量 是其 原始容量,並減去了一些可用效能的損失(例如,處理 IRQ 所花費的時間)。

請注意,CPU 的 容量 僅供 CFS 類使用,而 原始容量 則與類無關。 為了簡潔起見,本文件的其餘部分將交替使用術語 容量原始容量

1.3 平臺示例

1.3.1 相同的 OPP

考慮一個假設的雙核非對稱 CPU 容量系統,其中

  • work_per_hz(CPU0) = W

  • work_per_hz(CPU1) = W/2

  • 所有 CPU 均以相同的固定頻率執行

根據上述容量定義

  • capacity(CPU0) = C

  • capacity(CPU1) = C/2

為了與 Arm big.LITTLE 進行類比,CPU0 將是 big,而 CPU1 將是 LITTLE。

對於週期性地執行固定工作量的 workload,您將獲得如下執行跟蹤

CPU0 work ^
          |     ____                ____                ____
          |    |    |              |    |              |    |
          +----+----+----+----+----+----+----+----+----+----+-> time

CPU1 work ^
          |     _________           _________           ____
          |    |         |         |         |         |
          +----+----+----+----+----+----+----+----+----+----+-> time

CPU0 在系統中具有最高的容量 (C),並在 T 個時間單位內完成固定數量的工作 W。 另一方面,CPU1 的容量是 CPU0 的一半,因此僅在 T 中完成 W/2。

1.3.2 不同的最大 OPP

通常,具有不同容量值的 CPU 也具有不同的最大 OPP。 考慮與上述相同的 CPU(即,相同 work_per_hz())

  • max_freq(CPU0) = F

  • max_freq(CPU1) = 2/3 * F

這會產生

  • capacity(CPU0) = C

  • capacity(CPU1) = C/3

執行與 1.3.1 中描述的相同工作負載,每個 CPU 以其最大頻率執行,導致

CPU0 work ^
          |     ____                ____                ____
          |    |    |              |    |              |    |
          +----+----+----+----+----+----+----+----+----+----+-> time

                           workload on CPU1
CPU1 work ^
          |     ______________      ______________      ____
          |    |              |    |              |    |
          +----+----+----+----+----+----+----+----+----+----+-> time

1.4 表示注意事項

應該注意的是,使用單個值來表示 CPU 效能差異在某種程度上是一個有爭議的點。 兩種不同 µarch 之間的相對效能差異在整數運算中可能是 X%,在浮點運算中可能是 Y%,在分支中可能是 Z%,依此類推。 儘管如此,目前使用這種簡單方法的結果令人滿意。

2. 任務利用率

2.1 介紹

容量感知排程需要用 CPU 容量來表示任務的需求。 每個排程器類都可以不同地表達這一點,雖然任務利用率是 CFS 特有的,但為了引入更通用的概念,在這裡描述它是很方便的。

任務利用率是一個百分比,旨在表示任務的吞吐量要求。 它的一個簡單近似值是任務的佔空比,即

task_util(p) = duty_cycle(p)

在具有固定頻率的 SMP 系統上,100% 的利用率表示該任務是一個忙迴圈。 相反,10% 的利用率表明它是一個小型週期性任務,花費更多的時間處於休眠狀態而不是執行。 可變 CPU 頻率和非對稱 CPU 容量使這個問題複雜化; 以下各節將對此進行擴充套件。

2.2 頻率不變性

需要考慮的一個問題是,工作負載的佔空比會受到 CPU 當前執行的 OPP 的直接影響。 考慮以給定的頻率 F 執行週期性工作負載

CPU work ^
         |     ____                ____                ____
         |    |    |              |    |              |    |
         +----+----+----+----+----+----+----+----+----+----+-> time

這會產生 duty_cycle(p) == 25%。

現在,考慮以頻率 F/2 執行相同的工作負載

CPU work ^
         |     _________           _________           ____
         |    |         |         |         |         |
         +----+----+----+----+----+----+----+----+----+----+-> time

這會產生 duty_cycle(p) == 50%,儘管該任務在兩次執行中都具有完全相同的行為(即,執行相同數量的工作)。

可以使用以下公式使任務利用率訊號具有頻率不變性

task_util_freq_inv(p) = duty_cycle(p) * (curr_frequency(cpu) / max_frequency(cpu))

將此公式應用於以上兩個示例會產生 25% 的頻率不變任務利用率。

2.3 CPU 不變性

CPU 容量對任務利用率具有類似的影響,因為在具有不同容量值的 CPU 上執行相同的工作負載將產生不同的佔空比。

考慮 1.3.2 中描述的系統,即

- capacity(CPU0) = C
- capacity(CPU1) = C/3

在每個 CPU 上以其最大頻率執行給定的週期性工作負載將導致

CPU0 work ^
          |     ____                ____                ____
          |    |    |              |    |              |    |
          +----+----+----+----+----+----+----+----+----+----+-> time

CPU1 work ^
          |     ______________      ______________      ____
          |    |              |    |              |    |
          +----+----+----+----+----+----+----+----+----+----+-> time

IOW,

  • 如果 p 在 CPU0 上以其最大頻率執行,則 duty_cycle(p) == 25%

  • 如果 p 在 CPU1 上以其最大頻率執行,則 duty_cycle(p) == 75%

可以使用以下公式使任務利用率訊號具有 CPU 不變性

task_util_cpu_inv(p) = duty_cycle(p) * (capacity(cpu) / max_capacity)

其中 max_capacity 是系統中最高的 CPU 容量值。 將此公式應用於上述示例會產生 25% 的 CPU 不變任務利用率。

2.4 不變的任務利用率

為了獲得真正不變的訊號,頻率不變性和 CPU 不變性都需要應用於任務利用率。 因此,對於給定任務 p,CPU 和頻率不變的任務利用率的偽公式為

                                   curr_frequency(cpu)   capacity(cpu)
task_util_inv(p) = duty_cycle(p) * ------------------- * -------------
                                   max_frequency(cpu)    max_capacity

換句話說,不變的任務利用率描述了任務的行為,就好像它在系統中容量最高的 CPU 上以其最大頻率執行一樣。

以下各節中對任務利用率的任何提及都將暗示其不變形式。

2.5 利用率估計

如果沒有水晶球,則無法在任務首次變為可執行狀態時準確預測任務的行為(因此也無法準確預測任務利用率)。 CFS 類維護了基於 Per-Entity Load Tracking (PELT) 機制的少量 CPU 和任務訊號,其中一個訊號產生平均利用率(而不是瞬時利用率)。

這意味著,雖然容量感知排程標準在編寫時考慮了“真實”任務利用率(使用水晶球),但該實現將永遠只能使用其估計量。

3. 容量感知排程要求

3.1 CPU 容量

Linux 當前無法自行計算出 CPU 容量,因此需要將此資訊傳遞給它。 架構必須為此定義 arch_scale_cpu_capacity()。

arm、arm64 和 RISC-V 架構直接將其對映到 arch_topology 驅動程式的 CPU 縮放資料,該資料來源於 capacity-dmips-mhz CPU 繫結; 請參閱 Documentation/devicetree/bindings/cpu/cpu-capacity.txt。

3.2 頻率不變性

如 2.2 中所述,容量感知排程需要頻率不變的任務利用率。 架構必須為此定義 arch_scale_freq_capacity(cpu)。

實現此函式需要確定每個 CPU 以哪個頻率執行。 實現此目的的一種方法是利用其增量速率與 CPU 當前頻率(x86 上的 APERF/MPERF,arm64 上的 AMU)成比例的硬體計數器。 另一種方法是直接掛鉤到 cpufreq 頻率轉換,前提是核心知道切換到的頻率(arm/arm64 也採用了這種方法)。

4. 排程器拓撲

在構建 sched domains 期間,排程器將確定系統是否顯示非對稱 CPU 容量。 如果是這樣的話

  • 將啟用 sched_asym_cpucapacity 靜態鍵。

  • 將在跨越所有唯一 CPU 容量值的最低 sched_domain 級別設定 SD_ASYM_CPUCAPACITY_FULL 標誌。

  • 將為跨越具有任何範圍的不對稱性的 CPU 的任何 sched_domain 設定 SD_ASYM_CPUCAPACITY 標誌。

sched_asym_cpucapacity 靜態鍵旨在保護迎合非對稱 CPU 容量系統的程式碼段。 但請注意,該鍵是系統範圍的。 想象一下使用 cpusets 的以下設定

capacity    C/2          C
          ________    ________
         /        \  /        \
CPUs     0  1  2  3  4  5  6  7
         \__/  \______________/
cpusets   cs0         cs1

可以透過以下方式建立

mkdir /sys/fs/cgroup/cpuset/cs0
echo 0-1 > /sys/fs/cgroup/cpuset/cs0/cpuset.cpus
echo 0 > /sys/fs/cgroup/cpuset/cs0/cpuset.mems

mkdir /sys/fs/cgroup/cpuset/cs1
echo 2-7 > /sys/fs/cgroup/cpuset/cs1/cpuset.cpus
echo 0 > /sys/fs/cgroup/cpuset/cs1/cpuset.mems

echo 0 > /sys/fs/cgroup/cpuset/cpuset.sched_load_balance

由於系統中存在 CPU 容量不對稱,因此將啟用 sched_asym_cpucapacity 靜態鍵。 但是,CPU 0-1 的 sched_domain 層級結構跨越單個容量值:SD_ASYM_CPUCAPACITY 未在該層級結構中設定,它描述了一個 SMP 孤島,應將其視為如此。

因此,保護迎合非對稱 CPU 容量的程式碼路徑的“規範”模式是

  • 檢查 sched_asym_cpucapacity 靜態鍵

  • 如果已啟用,則還檢查 sched_domain 層級結構中是否存在 SD_ASYM_CPUCAPACITY(如果相關,即,程式碼路徑針對特定的 CPU 或其組)

5. 容量感知排程實現

5.1 CFS

5.1.1 容量適應性

CFS 的主要容量排程標準是

task_util(p) < capacity(task_cpu(p))

這通常被稱為容量適應性標準,即 CFS 必須確保任務“適應”其 CPU。 如果違反了該標準,則該任務需要完成的工作將超過其 CPU 可以提供的:它將是 CPU 密集型。

此外,uclamp 允許使用者空間為任務指定最小和最大利用率值,可以透過 sched_setattr() 或透過 cgroup 介面(請參閱 控制組 v2)。 顧名思義,這可以用於在先前的標準中限制 task_util()。

5.1.2 喚醒 CPU 選擇

CFS 任務喚醒 CPU 選擇遵循上述容量適應性標準。 最重要的是,uclamp 用於限制任務利用率值,這使使用者空間可以更好地控制 CFS 任務的 CPU 選擇。 IOW,CFS 喚醒 CPU 選擇搜尋滿足以下條件的 CPU

clamp(task_util(p), task_uclamp_min(p), task_uclamp_max(p)) < capacity(cpu)

透過使用 uclamp,使用者空間可以透過為其提供較低的 uclamp.max 值來允許忙迴圈(100% 利用率)在任何 CPU 上執行。 相反,可以透過為其提供較高的 uclamp.min 值來強制小型週期性任務(例如,10% 利用率)在效能最高的 CPU 上執行。

注意

CFS 中的喚醒 CPU 選擇可以被 Energy Aware Scheduling (EAS) 所掩蓋,該排程在 Energy Aware Scheduling 中進行了描述。

5.1.3 負載平衡

當任務很少休眠(如果有的話)時,即很少喚醒(如果有的話)時,喚醒 CPU 選擇中會出現病態情況。 考慮

w == wakeup event

capacity(CPU0) = C
capacity(CPU1) = C / 3

                         workload on CPU0
CPU work ^
         |     _________           _________           ____
         |    |         |         |         |         |
         +----+----+----+----+----+----+----+----+----+----+-> time
              w                   w                   w

                         workload on CPU1
CPU work ^
         |     ____________________________________________
         |    |
         +----+----+----+----+----+----+----+----+----+----+->
              w

此工作負載應在 CPU0 上執行,但如果任務要麼

  • 從一開始就沒有正確排程(初始利用率估計不準確)

  • 從一開始就正確排程,但突然需要更多的處理能力

那麼它可能會變得 CPU 密集型,IOW task_util(p) > capacity(task_cpu(p));違反了 CPU 容量排程標準,並且可能不再有任何喚醒事件可以透過喚醒 CPU 選擇來解決此問題。

處於這種情況的任務被稱為“不適應”任務,並且用於處理這種情況的機制也具有相同的名稱。 不適應任務遷移利用 CFS 負載平衡器,更具體地說是主動負載平衡部分(用於遷移當前正在執行的任務)。 當發生負載平衡時,如果不適應任務可以遷移到具有比當前 CPU 更多容量的 CPU,則將觸發不適應的主動負載平衡。

5.2 RT

5.2.1 喚醒 CPU 選擇

RT 任務喚醒 CPU 選擇搜尋滿足以下條件的 CPU

task_uclamp_min(p) <= capacity(task_cpu(cpu))

同時仍然遵循通常的優先順序約束。 如果沒有候選 CPU 可以滿足此容量標準,則遵循嚴格的基於優先順序的排程,並且忽略 CPU 容量。

5.3 DL

5.3.1 喚醒 CPU 選擇

DL 任務喚醒 CPU 選擇搜尋滿足以下條件的 CPU

task_bandwidth(p) < capacity(task_cpu(p))

同時仍然遵守通常的頻寬和截止日期約束。 如果沒有候選 CPU 可以滿足此容量標準,則該任務將保留在其當前 CPU 上。