自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

不改一行業(yè)務(wù)代碼,飛書 iOS 低端機啟動優(yōu)化實踐

精選
運維
本文將結(jié)合飛書啟動優(yōu)化,給出選取 GCD 隊列的最佳實踐,也提供針對低端機的啟動優(yōu)化思路。

作者|徐霜晴

引言

在啟動優(yōu)化時,我們常常通過增加并發(fā)的方式來減輕主線程的耗時。而在 iOS 中,GCD 是并發(fā)編程最常用的框架。增加并發(fā)是否是啟動優(yōu)化的良策?開發(fā)者適合選用哪個優(yōu)先級的 GCD 隊列?本文將結(jié)合飛書啟動優(yōu)化,給出選取 GCD 隊列的最佳實踐,也提供針對低端機的啟動優(yōu)化思路。

應(yīng)用此思路,我們在未修改飛書業(yè)務(wù)邏輯的情況下,在飛書低端機上,取得了不錯的用戶體驗收益:首屏展示時間優(yōu)化 100ms,消息列表首刷時間優(yōu)化 1500ms。

低端機的特性

通過 Instruments 的 App Launch 功能,我們能看到 App 啟動時的線程狀態(tài)、Time Profiler 等信息。其中,我們發(fā)現(xiàn)不同設(shè)備在啟動時的表現(xiàn)有很大差異。??

以 iPhone 7p(低端)和 iPhone 12(高端)舉例,它們的設(shè)備參數(shù)分別為:

設(shè)備


CPU 參數(shù)

實際核數(shù)

ProcessInfo.processInfo.activeProcessorCount

跑滿的 CPU 占比(Xcode 測試)

iPhone 7p


A10 芯片[1],2 高性能 + 2 低功耗,但是只有 2 核能同時工作

2


200%


iPhone 12

A14 芯片[2],2 高性能 + 4 低功耗

6

600%


啟動飛書時,我們通過 Instruments 觀察兩個設(shè)備的線程狀態(tài),經(jīng)過統(tǒng)計發(fā)現(xiàn),iPhone 7p 上,主線程 Preempted 和 Runnable 狀態(tài)的占比高達(dá) 21%。Instruments 的圖中能看到主線程大片被搶占。

圖片

一個典型的局部,能看到主線程是 preempted 狀態(tài),CPU0 在執(zhí)行其他進(jìn)程,CPU1 在執(zhí)行 GCD 線程。

而 iPhone 12,主線程 Preempted 和 Runnable 狀態(tài)占比則只占 1%從這里我們能發(fā)現(xiàn):對低端機來說,CPU 已經(jīng)成為了啟動的瓶頸,“增大并發(fā)”已不是一個萬能的啟動優(yōu)化措施,而想辦法減少其他線程對主線程的搶占,可能會是優(yōu)化思路。

GCD queue 對主線程的搶占評測

為了評估“減少其他線程對主線程的搶占”是否是一個可行的優(yōu)化思路,我們首先需要弄明白,主線程被搶占的程度會有多大?

我們可以使用 Demo 制造一些極端場景,了解極端場景下,主線程有多少比例會被其他線程搶占,因此有了如下 Demo 實驗:

實驗組1:

  • 異步線程 QoS:DispatchQoS.userInteractive

代碼:

for _ in 1...100 {
let queue = DispatchQueue.init(label: "serialQueue", qos: .userInteractive)
queue.async {
while true {
}
}
}
while true {
}
  • qos_class_self 數(shù)值:33
  • 主線程 Preempted + Runnable 占比:74%

實驗組2:

異步線程 QoS:不指定 QoS 或 DispatchQoS.userInitiated

代碼:

for _ in 1...100 {
let queue = DispatchQueue.init(label: "serialQueue")
queue.async {
while true {
}
}
}
while true {
}
  • qos_class_self 數(shù)值:25
  • 主線程 Preempted + Runnable 占比:73%

實驗組3:

  • 異步線程 QoS:DispatchQoS.utility?
  • 代碼:
for _ in 1...100 {
let queue = DispatchQueue.init(label: "serialQueue", qos: .utility)
queue.async {
while true {
}
}
}
while true {
}
  • qos_class_self 數(shù)值:17
  • 主線程 Preempted + Runnable 占比:1.3%

實驗組4:

  • 異步線程 QoS:DispatchQoS.background?
  • 代碼:
for _ in 1...100 {
let queue = DispatchQueue.init(label: "serialQueue", qos: .background)
queue.async {
while true {
}
}
}
while true {
}
  • qos_class_self 數(shù)值:9
  • 主線程 Preempted + Runnable 占比:1.3%?

不指定 QoS 下,一個極端 Demo,啟動期間主線程長時間處于 preempted 狀態(tài),一直無法得到 running 的機會

圖片

從中我們能看到幾個結(jié)論:

  1. 不指定 QoS 時,自行創(chuàng)建的 GCD queue 的 QoS 是 User-Initiated
  2. User-Initiated 及以上優(yōu)先級,對主線程會有嚴(yán)重?fù)屨棘F(xiàn)象;而 Utility 和 Background 則幾乎不會搶占主線程。

另外,我們也做測試驗證了,pthread_create 創(chuàng)建的線程,也有類似的搶占現(xiàn)象。

QoS 和 Priority

看到 iPhone 7p 上主線程被其他線程搶占,我們可能會有疑問:主線程不應(yīng)該是優(yōu)先級最高的么?怎么還會被其他線程搶占?

這里,我們需要理解一下 QoS 和線程 priority 兩個概念。

QoS(quality of service)意指服務(wù)質(zhì)量,它影響線程優(yōu)先級(priority),也影響 I/O 吞吐、 CPU 吞吐等指標(biāo)[3]。開發(fā)者可以用 qos_class_self() 接口獲得當(dāng)前線程 / 隊列的 QoS。

蘋果對于每個任務(wù)應(yīng)該選用哪個 QoS,也有一些指導(dǎo)意見[4]:

圖片

QoS 和 priority 確實有對應(yīng)關(guān)系,參考 xnu 源碼和實驗結(jié)果,對應(yīng)關(guān)系為:?

QoS

Priority

User-Interactive

46,對于 UI 線程是 47

User-Initiated

37

Utility

20

Background

4

同時,線程的 priority 會隨著執(zhí)行動態(tài)調(diào)整。測試中我們會發(fā)現(xiàn),主線程的 priority 在運行開始時是 QoS User-Interactive 對應(yīng)的 47,但隨著運行會出現(xiàn)下降的情況。

圖片

官方文檔[5]中解釋了線程 priority 變化的原因,priority 由 Mach scheduler 控制,為了防止計算密集的線程壟斷資源,各個線程的 priority 會實時調(diào)整。

All of these mechanisms are operating continually in the Mach scheduler. This means that threads are frequently moving up or down in priority based upon their behavior and the behavior of other threads in the system.

進(jìn)一步閱讀 xnu 內(nèi)核的源碼[6],我們發(fā)現(xiàn),線程 priority 的變化,是由各個 Mach scheduler 實現(xiàn)的 compute_timeshare_priority 接口控制的。在 iOS 使用的 Mach scheduler 中,compute_timeshare_priority 為同一個實現(xiàn) sched_compute_timeshare_priority。線程調(diào)度時的 priority,會在線程固有 priority 的基礎(chǔ)上,結(jié)合當(dāng)前線程的 CPU 占用情況和當(dāng)前設(shè)備的整體負(fù)載進(jìn)行調(diào)整。

在這個實現(xiàn)中,我們能看到 Mach scheduler 對 priority 的調(diào)整會有一個極限:對于原先 priority = 47 的線程來說,向下調(diào)整的極限是 47 - ((BASEPRI_FOREGROUND - BASEPRI_DEFAULT) + 2) = 29。這和我們用多個設(shè)備測試到的結(jié)果吻合:主線程執(zhí)行時,priority 的最低值是 29,依然高于 Utility 對應(yīng)的 priority 20。

這也解釋了,為什么 Demo 中當(dāng)異步線程的 QoS 是 Utility 時,就幾乎無法對主線程造成搶占。

優(yōu)化落地

通過 Demo 實驗,一個啟動優(yōu)化思路產(chǎn)生了:在飛書中,大量異步隊列的 QoS 是 User-Initiated,盡管這一 QoS 低于主線程的 User-Interactive,但依然可能對主線程造成搶占;那么,如果將異步隊列的 QoS 調(diào)低到 Utility,是不是就可以優(yōu)先保障主線程執(zhí)行,讓首屏更早展現(xiàn)出來?

經(jīng)過一些粗暴的實驗,我們證實了飛書在這個思路上存在優(yōu)化空間。但另一個問題隨之而來:如何兼顧首屏、消息列表首刷等多個指標(biāo)?

考慮消息列表首刷的場景:獲取到最新的消息,不僅僅需要主線程構(gòu)建 UI,還需要依賴數(shù)據(jù)庫讀取、網(wǎng)絡(luò)請求等異步操作。如果我們粗暴地將所有異步隊列的 QoS 調(diào)低,首屏確實能更快展現(xiàn),但消息列表的首刷則隨著異步操作的變慢更劣化了。這對用戶體驗反而帶來了負(fù)向影響。

梳理出哪些異步操作是首刷依賴的,確保這些隊列的 QoS ,是優(yōu)化中非常重要的一環(huán)。我們首先通過不斷用 Instruments 測試、閱讀代碼梳理出了首版白名單隊列,并在線下和線上驗證了首屏、首刷等關(guān)鍵指標(biāo)的優(yōu)化收益。在后來的迭代中,我們又開發(fā)了線下工具,通過在線下 hook dispatch_async 等函數(shù),記錄下首刷等時機依賴的 GCD 隊列,達(dá)成了白名單隊列自動生成的能力。

效果分析

這一優(yōu)化在線上產(chǎn)生了不錯的體驗優(yōu)化效果:

啟動首屏展現(xiàn)時間優(yōu)化 100ms

通過調(diào)整異步線程的 QoS,啟動期間主線程 CPU 搶占現(xiàn)象有明顯降低。更多計算資源集中到主線程,使得首屏展示速度明顯加快。

消息列表首刷時間優(yōu)化 1500ms

通過對消息列表首刷依賴的任務(wù)的分析,我們調(diào)低了無關(guān)線程的 QoS,這也讓首刷依賴的數(shù)據(jù)庫讀取、網(wǎng)絡(luò)請求等任務(wù)得到了更多資源,加速了它們的執(zhí)行。

總結(jié)

“增加并發(fā)”在一定范圍內(nèi)可以作為啟動優(yōu)化的方案,但在低端機上,CPU 已經(jīng)成為瓶頸,并發(fā)時異步線程對主線程的搶占也需要引起重視。

GCD 提供了四種 QoS 給開發(fā)者使用,官方也為這四種 QoS 提供了最佳實踐建議。

經(jīng)過評測和源碼推理,User-Interactive 和 User-Initiated 對主線程有明顯搶占,Utility 和 Background 對主線程的搶占極少。開發(fā)者創(chuàng)建的 GCD 隊列,默認(rèn)的 QoS 實際為 User-Initiated。因此在啟動期間(或者任何耗時敏感期間),與啟動無直接關(guān)系的 queue,應(yīng)該主動設(shè)置為 Utility 或 Background,減少對主線程的搶占。

通過飛書上落地優(yōu)化,我們能得出結(jié)論:對線程或 GCD queue 調(diào)整 QoS,能在不改變啟動業(yè)務(wù)邏輯的情況下取得顯著收益。

當(dāng)然,比事后優(yōu)化更好的操作,是在編碼時就充分了解不同 QoS 的行為特性,選用最適合的 QoS。?

責(zé)任編輯:未麗燕 來源: 字節(jié)跳動技術(shù)團隊
相關(guān)推薦

2024-08-16 14:28:21

2016-12-02 08:53:18

Python一行代碼

2017-11-20 14:46:27

命令代碼

2021-06-11 14:15:55

代碼前端項目

2014-02-12 13:43:50

代碼并行任務(wù)

2022-04-09 09:11:33

Python

2017-04-05 11:10:23

Javascript代碼前端

2021-12-29 09:34:49

Log4j漏洞代碼

2020-08-19 10:30:25

代碼Python多線程

2020-09-09 16:00:22

Linux進(jìn)程

2021-11-02 16:25:41

Python代碼技巧

2021-08-31 09:49:37

CPU執(zhí)行語言

2017-04-13 19:20:18

Python代碼并行任務(wù)

2023-09-12 10:10:57

開發(fā)者工具開源

2023-08-09 17:35:11

開源模型

2020-09-28 12:34:38

Python代碼開發(fā)

2019-12-25 14:08:50

Pandas數(shù)據(jù)計算

2020-08-12 14:54:00

Python代碼開發(fā)

2022-09-28 10:12:50

Python代碼可視化

2021-12-26 12:10:21

React組件前端
點贊
收藏

51CTO技術(shù)棧公眾號