<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          iOS 15 如何讓 App 啟動(dòng)更快?

          共 5682字,需瀏覽 12分鐘

           ·

          2021-08-05 20:37

          譯者 | 無阻我飛揚(yáng)  責(zé)編 | 晉兆雨
          出品 | CSDN(ID:CSDNnews)
          WWDC21中最有趣的特性被深深地隱藏在 Xcode 13發(fā)布說明中:
          部署在 macOS 12 或 iOS 15 及更高版本操作系統(tǒng)上的所有程序及 dylibs現(xiàn)在都使用鏈?zhǔn)叫迯?fù)格式。這種格式使用不同的加載命令和 LINKEDIT 數(shù)據(jù),不能在低版本的操作系統(tǒng)上運(yùn)行或加載。
          目前還沒有任何文獻(xiàn)或會(huì)議可以了解更多有關(guān)于此更改的信息,但我們可以對(duì)其進(jìn)行逆向工程,以了解 Apple 在新版本上有何不同,它是否優(yōu)化了App的啟動(dòng)時(shí)間。首先,了解控制App啟動(dòng)的程序的一些背景知識(shí)。

          認(rèn)識(shí)dyld
          dyld是蘋果的動(dòng)態(tài)鏈接器,是蘋果操作系統(tǒng)一個(gè)重要組成部分,是每個(gè)App的入口點(diǎn)。它負(fù)責(zé)讓APP的代碼做好運(yùn)行準(zhǔn)備,因此對(duì)dyld的任何改進(jìn)都會(huì)使得App啟動(dòng)時(shí)間縮短。在調(diào)用main、運(yùn)行靜態(tài)初始化程序或設(shè)置 Objective-C運(yùn)行時(shí)間之前,dyld負(fù)責(zé)執(zhí)行修正操作,包括變基和綁定操作,這些操作修改App二進(jìn)制文件中的指針以包含在運(yùn)行時(shí)有效的地址。想要了解它們?nèi)绾芜\(yùn)行,可以使用 dyldinfo 命令行工具。
          % xcrun dyldinfo -rebase -bind Snapchat.app/Snapchatrebase information (from compressed dyld info):segment section address type__DATA __got 0x10748C0C8 pointer...bind information:segment section address type addend dylib symbol__DATA __const 0x107595A70 pointer 0 libswiftCore _$sSHMp
          這意味著地址 0x10748C0C8 位于 __DATA/__got,需要按一個(gè)常量值進(jìn)行移位。地址 0x107595A70 在 __DATA/__const, 應(yīng)該指向 Hashable[1] 的協(xié)議描述符在libswiftCore.dylib
          dyld 使用 LC_DYLD_INFO 載入命令和 dyld_info_command 結(jié)構(gòu)確定二進(jìn)制文件中變基、綁定和導(dǎo)出符號(hào)[2]的位置和大小 。Emerge (聲明:我是創(chuàng)始人),解析這些數(shù)據(jù),直觀了解它們對(duì)二進(jìn)制大小的貢獻(xiàn),建議鏈接器標(biāo)志使它們變得更小:

           

          一種新的格式

          當(dāng)?shù)谝淮紊蟼饕粋€(gè)為iOS15構(gòu)建的App時(shí),通過 Emerge,并沒有看到dyld修正的效果。因?yàn)槿鄙?/span>LC_DYLD_INFO_ONLY加載命令,它已被替換為LC_DYLD_CHAINED_FIXUPS 和 LC_DYLD_EXPORTS_TRIE
          % otool -l iOS14Example.app/iOS14Example | grep LC_DYLD      cmd LC_DYLD_INFO_ONLY% otool -l iOS15Example.app/iOS15Example | grep LC_DYLD      cmd LC_DYLD_CHAINED_FIXUPS      cmd LC_DYLD_EXPORTS_TRIE
          導(dǎo)出數(shù)據(jù)與之前完全相同,樹的每個(gè)節(jié)點(diǎn)代表符號(hào)名稱的一部分。 

          iOS 15 中唯一的變化是數(shù)據(jù)現(xiàn)在由 linkedit_data_command 引用,該命令包含第一個(gè)節(jié)點(diǎn)的偏移量。為了驗(yàn)證這一點(diǎn),我寫了一個(gè)簡短的 Swift App來解析 iOS 15 二進(jìn)制文件并打印每個(gè)符號(hào):
          let bytes = (try! Data(contentsOf: url) as NSData).bytes      bytes.processLoadComands { load_command, pointer in        if load_command.cmd == LC_DYLD_EXPORTS_TRIE {          let dataCommand = pointer.load(as: linkedit_data_command.self)          bytes.advanced(by: Int(dataCommand.dataoff)).readExportTrie()        }      }    
          extension UnsafeRawPointer { func readExportTrie() { var frontier = readNode(name: "") guard !frontier.isEmpty else { return }
          repeat { let (prefix, offset) = frontier.removeFirst() let children = advanced(by: Int(offset)).readNode(name: prefix) for (suffix, offset) in children { frontier.append((prefix + suffix, offset)) } } while !frontier.isEmpty }
          // Returns an array of child nodes and their offset func readNode(name: String) -> [(String, UInt)] { guard load(as: UInt8.self) == 0 else { // This is a terminal node print("symbol name \(name)") return [] } let numberOfBranches = UInt(advanced(by: 1).load(as: UInt8.self)) var mutablePointer = self.advanced(by: 2) var result = [(String, UInt)]() for _ in 0..<numberOfBranches { result.append( (mutablePointer.readNullTerminatedString(), mutablePointer.readULEB())) } return result } }



          真正的變化在 LC_DYLD_CHAINED_FIXUPS。 在 iOS 15 之前的版本,變基、綁定和延遲綁定分別存儲(chǔ)在單獨(dú)的表中。現(xiàn)在它們已組合成鏈,在這個(gè)新的加載命令中,包含鏈起點(diǎn)的指針: 

          App二進(jìn)制文件被分解成多個(gè)段,每個(gè)段都包含一個(gè)可以綁定或變基的修復(fù)鏈(不再有延遲綁定)。二進(jìn)制文件中的每個(gè) 64 位 rebase[3] 定位,對(duì)它指向的偏移量以及到下一個(gè)修正的偏移量進(jìn)行編碼,如以下結(jié)構(gòu)所示:
          struct dyld_chained_ptr_64_rebase{uint64_t target : 36,high8 : 8,reserved : 7, // 0snext : 12,bind : 1; // Always 0 for a rebase};
          指針對(duì)象使用36位,足以容納 23 ? = 64GB 的二進(jìn)制文件,12 位用于提供下一個(gè)修正的偏移量(步幅 = 4)。因此,它可以指向 2 12 * 4 = 16kb范圍內(nèi)的任何位置——正是 iOS 上的頁面大小。
          這種非常緊湊的編碼意味著遍歷鏈的整個(gè)過程可以包含在二進(jìn)制的現(xiàn)有大小內(nèi)。 在我的測試中,超過 50% 的 dyld 數(shù)據(jù)對(duì)二進(jìn)制大小的貢獻(xiàn)被保存,因?yàn)橹槐A袅松倭吭獢?shù)據(jù)用來指示每個(gè)頁面上的第一個(gè)修正。最終結(jié)果是Swift App的大小減少了 1mb 以上。
          這個(gè)過程的源代碼在 MachOLoaded.cpp 中 ,二進(jìn)制設(shè)計(jì)在 /usr/include/macho-o/fixup-chains.h

          排序問題

          要理解這種改變背后的動(dòng)機(jī),我們必須注意App啟動(dòng)時(shí)開銷最大的操作——缺頁異常。在App啟動(dòng)期間訪問文件系統(tǒng)上的代碼時(shí),需要通過缺頁異常將其從文件寫入到內(nèi)存。App二進(jìn)制文件中的每個(gè) 16kb區(qū)間都映射到內(nèi)存中的一個(gè)頁面。一旦頁面被修改,它就需要在App運(yùn)行期間一直保留在 RAM 中(稱為臟頁面)。iOS 通過壓縮最近未使用的頁面來優(yōu)化這一點(diǎn)。

          App啟動(dòng)時(shí)的修正需要更改App二進(jìn)制文件中的地址,因此整個(gè)頁面都被標(biāo)記為臟頁面。讓我們看看在app啟動(dòng)期間修正程序使用了多少頁面:
          % xcrun dyldinfo -rebase Snapchat.app/Snapchat > rebases% ruby -e 'puts IO.read("rebases").split("\n").drop(2).map { |a| a.split(" ")[2].to_i(16) / 16384 }.uniq.count'1554% xcrun dyldinfo -bind Snapchat.app/Snapchat > binds450
          對(duì)于表的格式,首先解析變基,然后是綁定。這意味著變基需要許多缺頁異常,并且最終主要是 IO 綁定 [4]。另一方面,綁定訪問了30% 的變基使用的頁面,有效地進(jìn)行了第二次內(nèi)存?zhèn)鬟f。
          現(xiàn)在在 iOS 15版本中,鏈?zhǔn)叫拚龑⒚總€(gè)內(nèi)存頁面的所有更改組合在一起。dyld 現(xiàn)在可以通過一次遍歷內(nèi)存來更快地處理它們,同時(shí)完成變基和綁定。 這使得諸如內(nèi)存壓縮器之類的操作系統(tǒng)功能能夠利用眾所周知的排序,而無需在綁定期間返回并解壓縮舊頁面。由于這些改變,dyld中的變基函數(shù)變成了一個(gè)空操作:

           https://opensource.apple.com/source/dyld/dyld-851.27/src/ImageLoaderMachOCompressed.cpp.auto.html
          總的來說,這種改變主要影響對(duì) iOS App進(jìn)行逆向工程和探索動(dòng)態(tài)鏈接器細(xì)節(jié),這很好地提醒了大家,低級(jí)的內(nèi)存管理會(huì)影響App性能。雖然這種改變僅在iOS 15版本上的App有效,但請(qǐng)記住,仍然可以做很多事情來優(yōu)化App啟動(dòng)時(shí)間:
          • 減少動(dòng)態(tài)框架的數(shù)量

          • 減少應(yīng)用程序大小,從而減少內(nèi)存頁面的使用(這就是我制作 Emerge 的原因!)

          • 將代碼移出 +加載以及靜態(tài)初始化程序

          • 使用 更少的類

          • 將工作推遲到繪制第一個(gè)框架后

          參考鏈接:

          • [1] The symbol from dyldinfo is mangled, you can get the human readable name with xcrun swift-demangle '_$sSHMp'.
          • [2] Exports are the second piece of a bind. One binary binds to symbols exported from its dependencies.
          • [3] The same goes for binds, a pointer is actually a union of rebase and bind (dyld_chained_ptr_64_bind) with a single bit used to differentiate the two. Binds also require the imported symbol name which isn’t discussed here.
          • [4] https://asciiwwdc.com/2016/sessions/406
          原文鏈接:https://medium.com/geekculture/how-ios-15-makes-your-app-launch-faster-51cf0aa6c520
          作者:Noah Martin
          聲明:本文由CSDN翻譯,轉(zhuǎn)載請(qǐng)注明來源


          iOS 15 內(nèi)置原生壁紙下載
          優(yōu)酷 iOS 插件化頁面架構(gòu)方法
          這也行?iOS后臺(tái)鎖屏監(jiān)聽搖一搖
          189.31G iOS 學(xué)習(xí)資料分享
          iOS APP圖標(biāo)版本化

          瀏覽 41
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  艹逼大片 | 亚洲无码视频在线观看高清 | 白石真琴的AV成人片 | 午夜精品久久无码成人 | 五月丁香激情六月 |