您當前的位置:首頁 > 體育

數控面板解鎖 解密

作者:由 新代數控系統解鎖 發表于 體育時間:2022-03-24

數控面板解鎖 解密APP啟動最佳化大致分為

Main函式之前

Main函式

之後,Main函式之後的最佳化大多是使用

懶載入

的方式或者最佳化

業務載入順序

,下面重點看一下

Main函式之前

的最佳化。所謂冷啟動最佳化就是應用第一次載入進記憶體的最佳化。

Pre-main時間

Main函式之前都幹了什麼?

Xcode

新增環境變數

DYLD_PRINT_STATISTICS

,日誌看一下

pre-main

啟動時間,此處demo以本人實際專案為例。

Xcode

執行專案,日誌輸出如下:

**Total pre-main time: 774。24 milliseconds (100。0%)** **dylib loading time: 138。83 milliseconds (17。9%)** **rebase/binding time: 5。88 milliseconds (0。7%)** **ObjC setup time: 83。71 milliseconds (10。8%)** **initializer time: 545。80 milliseconds (70。4%)** **slowest intializers :** **libSystem。B。dylib : 9。07 milliseconds (1。1%)** **libMainThreadChecker。dylib : 50。17 milliseconds (6。4%)** **libglInterpose。dylib : 185。33 milliseconds (23。9%)** **推送助手 : 525。02 milliseconds (67。8%)** 複製程式碼

可以看到

pre-main

也就是main函式之前總耗時

774。24

毫秒

dylib loading

動態庫的載入

。儘量減少自定義動態庫,蘋果建議大於

6個

的話儘可能合併。

rebase/binding

:重定位/繫結。macho中的記憶體地址是偏移地址,需要加上首地址

ASLR

變成真實的虛擬記憶體地址,這就是

重定位

。區是於連結,連結是編譯時,繫結是執行時,繫結是繫結的共享快取的地址,外部符號越多繫結時間相對越長。

ObjC setup

OC類的註冊

。要儘可能的刪除沒有用到的類,雖然沒有用到,但是隻要存在,就會對類進行處理。

initializer time

load()以及建構函式

。load()

別做耗時

的事情,這是啟動時間最佳化的重點,一般是

最佳化業務邏輯

slowest intializers

詳細列出了

initializer time

中具體耗時情況,我的專案中耗時最多的是主工程的載入,耗時

525。02

毫秒

對於

Main函式之前

的最佳化,先拋開主工程的載入,最佳化建議如下:

減少

自定義動態庫,控制在

6

個以內

刪除

沒有用到的類

load()別做耗時

的事情。

虛擬記憶體

最佳化主工程的載入之前,先了解一下什麼是

虛擬記憶體

,簡單總結如下。

一開始程式載入的記憶體是

物理記憶體

,但是軟體發展速度太快了導致物理記憶體不夠用,畢竟一個程式就佔用一塊物理記憶體,而且直接使用物理記憶體存在

安全隱患

,駭客很容易攻擊這塊記憶體。

為了讓記憶體安全以及記憶體夠用就產生了

虛擬記憶體

,現在我們所說的記憶體都是虛擬記憶體,

CPU

上的一個模組會把

虛擬記憶體

翻譯成

物理記憶體

,這樣虛擬記憶體和物理記憶體就有一個

對映表

,為了高效的地址翻譯,不可能一個位元組一個位元組的翻譯,於是出現了

記憶體分頁管理

,即以頁為單位翻譯,

iphone6s

之後一頁是

16

位元組,

iphon6s之前

一頁是

4

位元組,所以iphone6s之後啟動時間會比較快,因為記憶體翻譯很快。虛擬記憶體地址很大有4G,這樣就解決了記憶體不夠用的情況。

程式載入

不會把虛擬記憶體

根據對映表都載入進

物理記憶體

中,而是

用到哪一頁或哪幾頁就把用到的載入進去

。比如當程式訪問虛擬記憶體

p1

頁時,如果物理記憶體中沒有,就會發生

缺頁中斷

,這時就要把這塊虛擬記憶體載入進物理記憶體,ios程式的

冷啟動

是發生

缺頁中斷

最多的地方,因為這時的物理記憶體中沒有對應的虛擬記憶體,雖然載入進物理記憶體很快,但是如果有非常多的缺頁中斷,那麼還是會影響

效率

的。

總結:

過多的缺頁中斷

就會影響APP啟動速度,所以我們要儘可能的

減少缺頁中斷

。比如啟動APP時必須呼叫10個方法,但是這10個方法分佈在10個不同的頁中,那麼就會發生10次缺頁中斷,如果把啟動時必須呼叫的這10個方法放在同一個或者一兩個頁中,那麼就會

減少缺頁中斷的次數

。下面的重點就是如何

重排二進位制

減少缺頁中斷的次數。

二進位制重排前

Xcode

command+control+i

啟動

instruments

選中

System Trace

,點選啟動,當APP啟動後進入第一個頁面,點選暫停,這時就會記錄從啟動到第一個頁面的過程資料。搜尋

Main thread

檢視虛擬記憶體中

缺頁中斷情況

,如下圖。

分析:缺頁中斷有

384

次,耗時

58。69

毫秒,啟動耗時

62。99

毫秒,缺頁中斷耗時佔了大部分。最佳化缺頁中斷次數的前提是有沒有浪費,有浪費的缺頁中斷才需要最佳化。

啟動時函式的載入順序

我們上面說為了減少缺頁中斷,需要儘可能的把啟動時函式的呼叫放在同一個頁中,下面看下整個應用裡函式載入進記憶體中的順序。

Xcode->build setting->Link Map->Write Link Map File->YES

,編譯後在

bulid

中檢視這個

link map

檔案如下:

link map

中這些函式呼叫是根據

編譯順序排列

的,我們想要減少缺頁中斷,就要儘可能的把啟動時的函式呼叫

放在最前面

。可能你會覺得在Xcode中

重新排列

檔案的編譯順序就可以,但是我們是要把啟動時呼叫的

函式

放在最前面,而不是啟動時的檔案放在最前面,畢竟檔案中的函式有很多,但是隻有個別函式在啟動時會呼叫。

如何重排二進位制

那麼我們如何找出APP啟動時呼叫了哪些函式呢?

Clang

的插樁法可以很好的解決這個問題。根據Clang官方文件文件步驟如下:

1。

Xcode->build setting->Other C Flags->-fsanitize-coverage=func,trace-pc-guard

編寫程式生成。order檔案,比如gy。order,clang會根據此檔案對link map檔案中的函式重新排列,程式中需要實現的關鍵函式如下:

__sanitizer_cov_trace_pc_guard_init

:app中有多少個函式就會有多少個符號

__sanitizer_cov_trace_pc_guard

:hook所有回撥函式,

APP啟動時

呼叫了多少個函式,就會呼叫多少次該函式。clang在編譯時會把該函式插入到函式的開始位置。

具體demo如下:

#import #import //定義原子佇列 static OSQueueHead symbolList = OS_ATOMIC_QUEUE_INIT; //定義符號結構體 typedef struct { void * pc; void * next; } SYNode; //裡面反應了專案中符號的個數!! void __sanitizer_cov_trace_pc_guard_init(uint32_t *start, uint32_t *stop) { static uint64_t N; if (start == stop || *start) return; // printf(“INIT: %p %p\n”, start, stop); for (uint32_t *x = start; x < stop; x++) *x = ++N; } //HOOK一切的回撥函式!! void __sanitizer_cov_trace_pc_guard(uint32_t *guard) { void *PC = __builtin_return_address(0); //建立結構體 SYNode * node = malloc(sizeof(SYNode)); *node = (SYNode){PC,NULL}; //結構體入棧 OSAtomicEnqueue(&symbolList, node, offsetof(SYNode, next)); } //生成order檔案!! -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { //定義陣列 NSMutableArray * symbleNames = [NSMutableArray array]; while (YES) { SYNode * node = OSAtomicDequeue(&symbolList, offsetof(SYNode,next)); if (node == NULL) { break; } Dl_info info; dladdr(node->pc, &info); NSString * name = @(info。dli_sname);//轉字串 //給函式名稱新增 _,c函式或者block函式需要加上_,oc函式不需要 BOOL isObjc = [name hasPrefix:@“+[”] || [name hasPrefix:@“-[”]; NSString * symbolName = isObjc ? name : [@“_” stringByAppendingString:name]; [symbleNames addObject:symbolName]; } //反向遍歷陣列,最後一個元素是啟動時最先呼叫的 NSEnumerator * em = [symbleNames reverseObjectEnumerator]; NSMutableArray * funcs = [NSMutableArray arrayWithCapacity:symbleNames。count]; NSString * name; //去除重複呼叫的函式 while (name = [em nextObject]) { if (![funcs containsObject:name]) {//陣列沒有name [funcs addObject:name]; } } //去掉touchbegin自己,touchbegin是測試生成排序檔案的函式 [funcs removeObject:[NSString stringWithFormat:@“%s”,__func__]]; //寫入檔案 //1。程式設計字串 NSString * funcStr = [funcs componentsJoinedByString:@“\n”]; NSString * filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@“gy。order”]; NSData * file = [funcStr dataUsingEncoding:NSUTF8StringEncoding]; [[NSFileManager defaultManager] createFileAtPath:filePath contents:file attributes:nil]; NSLog(@“%@”,funcStr); } 複製程式碼

demo分析:

__sanitizer_cov_trace_pc_guard

,hook函式是多執行緒的,所以為了執行緒安全,需要定義一個

原子佇列

存放。

結構體入棧函式

OSAtomicEnqueue

offsetof(SYNode, next)

目的是指定存放下一個函式的位置。

執行專案點選螢幕,複製沙盒中生成的

gy。order

檔案放在專案根目錄下,

xcode->order File->。/gy。order

,同時需要刪除

Xcode->build setting->Other C Flags->-fsanitize-coverage=func,trace-pc-guard

配置。clang會根據gy。order重新排序函式編譯的順序,再來看一下link map是否重排成功了。

很明顯

link map

確實改變了,我們再

System Trace

看一下主線中缺頁中斷是否優化了。

對比一下沒排序前,缺頁中斷有

384

次,耗時58。69毫秒,啟動耗時62。99毫秒。現在缺頁中斷

271

,耗時40。03,啟動耗時44。58,啟動大概優化了不到20毫秒。目前測試的專案不是很大,如果專案很大的話,比如微信,那麼這個啟動最佳化還是很可觀的。