iOS编译优化--带你深入浅出了解HMap

前言:

编译阶段的优化除了组件二进制化可以实现提前编译 .O文件外,还有没有更进一步的优化方案呢?

首先看下 组件二进制化 = 二进制 +.h ,可以看到除了.a文件外还有.h文件参与编译,所以我们可以从.h编译的角度来考虑进一步的优化,而行之有效的方法就是利用Header Map 技术 。

首先了解下Header Map 是什么

我们知道Xcode是通过Header Search Path来查找引入到项目的头文件的,

#import AFNetworking.h 

xcode 编译时读取到目录时拼接上头文件名称

Header Search Path-> AFNetworking.h 文件目录+名称 => 路径

#import <AFNetworking/AFNetworking.h>

其中前半部分 AFNetworking/ 对于动态framework中它代表一个模块也就是module,它的作

用是用来映射头文件的,通过 umbrella header 可以递归导出子目录下的所有.h

而.a静态库也可以这样写,他代表的是真实目录

"${PODS_ROOT}/Headers/Public" #import <AFNetworking/AFNetworking.h>

"${PODS_ROOT}/Headers/Public/AFNetworking"  #import AFNetworking.h 

我们已知在编译过程中header是参与编译的,它的首次编译过程

1. 根据目录查找头文件(n个组件目录下去找某个头文件)

2. clang 编译header 成二进制 -> .pcm

可以看到系统本身是有对头文件的编译进行优化的,首次会把头文件编译成二进制,加快了下一次编译速度。

而通过目录的方式查找头文件,如cocoapods自动生成的debug.xcconfig文件,当项目中很多的文件就会有很多的Header Search Path目录,若在多个目录里面寻找一个头文件,明显会比较耗时,类越多的话查找时间就会越长,所以如果能把一个文件路径提前放在一个文件里面,直接去读取文件里面的路径的话应该会快很多,而Xcode本身就存在Use Header Map配置优化。

所以第一步头文件查找阶段中可以通过开启Xcode配置来进行优化,只需要在 Build Setting 中开启 Use Header Map 选项,就会为我们的主工程以及每个pod组件生成单独hmap文件(Xcode 会为 Pod 生成 5 个 hmap 文件),编译时Clang 就会在 hmap 文件里塞入了一份头文件名和头文件路径的映射表。(开启后可以查看.m文件的编译,发现在编译期底层通过-I参数链接了该组件下的hmap文件),

当再次编译时就会直接使用这些hmap快速找到对应的头文件,编译就会快很多了,不过对Xcode进行清理后这些也都会被删掉。

所以HMap就是系统用来提高头文件查找速度的,通过Header Search Path的设置来查找每个hmap文件,Header Search Path 其底层是-I, 接收两个参数,头文件的目录,指定hmap文件,所以会看到Header Search Path设置了工程以及组件的路径。而xcode通过这个包裹着头文件所在的目录的hmap去查找文件的时候会更快速。但若头文件比较少,hmap的作用越不明显所以不需要用到hmap文件,反而头文件数量越多效果会越明显。

下面是编译时生成的hmap,点开后可以查看文件所在具体路径

hmap文件如何生成?底层结构是什么样的?

首先我们探究下Header Map的底层结构

提前先说明下它其实是一组头文件信息映射表,它是以键值对的形式存在,Key 值是头文件的名称,Value 是头文件的实际物理路径(-I可以通过hmap解析到头文件所在的目录)

如果我们通过cat命令查看.hmap中的内容是下面这样的,前半部分是配置文件,后半部分存储真正的信息

那hmap其中的结构是什么样子的?

关于Hmap在LLVM的源码中有相关详细的介绍,Tests target中存在两个结构体HMapBucket和HMapHeader,并有将Hmap解析成HMapBucket和HMapHeader的源代码,以及如何生成hmap的源码。

Hmap的结构

可以看到hmap文件由HampHeader和HmapBucket以及一个string_table组成.

最上层是Hmapheader,保存了当前Hmap的详细信息,可以通过读取Hmapheader,知道当前二进制里面有多少个Bucket。

HmapBucket:保存了Hmap的具体信息,有Key、Prefix、Suffix三个字段代表了在string_table偏移量,可以根据偏移量计算出保存字符串的位置。

Key 代表具体头文件 --AFNetworking.h 

Prefix 代表头文件的前半部分(目录),Suffix表示头文件的后半部分(头文件名称),也就是Prefix + Suffix 代表一个头文件的完整路径。

比如target中包含3个头文件,则会有3个HmapBucket,Bucket的数量与头文件的数量相等,

Bucket中的偏移量,可以根据偏移量计算出保存字符串的位置,从而读取出头文件的路径。

string_table:头文件字符串

Bucket首地址: string_table首地址 = HMapHeader大小 + num *HMapBucket

具体如何读这里不再介绍,可以直接用开源工具DumpHeaderMap、hmap 工具来将文件读取成结构体,再从中读取出头文件的字符串。

解析出来的Hmap是下面这个样子:

    Hash bucket count: 4

    String table entry count: 4

    Max value length: 0 bytes

    - Bucket 0: Key TestApp-Bridging-Header.h -> Prefix /Users/cloud/Documents/iOS/TestApp/TestApp/, Suffix TestApp-Bridging-Header.h

    - Bucket 1: Key ViewController.h -> Prefix /Users/cloud/Documents/iOS/TestApp/, Suffix ViewController.h

    - Bucket 2: Key AppDelegate.h -> Prefix /Users/cloud/Documents/iOS/TestApp/, Suffix AppDelegate.h

    - Bucket 3: Key SceneDelegate.h -> Prefix /Users/cloud/Documents/iOS/TestApp/, Suffix SceneDelegate.h

如何生成Hmap

在LLVM的源码中的HeaderMapTest.cpp 中有关于生成Hmap详细介绍,大概步骤如下

创建MapFile容器Maker,Maker包含一个个的MapFile,也就是Bucket,其中8代表有八个Bucket,750代表大小

生成一个Bucket,然后将类名和路径以Bucket的形式保存

将文件导出到指定位置

最后重写 xcconfig 文件里的 Header Search Path 到对应的 hmap 文件上。

那是不是我们只要开启 Xcode 提供的 Use Header Map 就可以提升编译速度了呢?

Clang生成Hmap内容时,键值内容会随着使用场景产生不同的变化,例如头文件引用是在 "..." 的形式下,还是 <...> 的形式下,又或是在 Build Phase 里 Header 的配置情况。又或者你将头文件设置为 Public 的时候,在某些 hmap 中,它的 Key 值就为 PodA/ClassA,而将其设置为 project 的时候,它的 Key 值可能就是 ClassA,

而在基于 CocoaPods 管理项目的状况下,由于Xcode 和 CocoaPods 在工程和头文件上的的管理上理念的不同会给我们带来新的问题。

当我们构建的产物类型为 Static Library 的时候,CocoaPods 在创建头文件产物过程中,它的逻辑大致如下:

1:不论 podspec 里如何设置 public_header_files 和 private_header_files,相应的头文件都会被设置为 Project 类型。

2:如果 podspec 里未标注 Public 和 Private 的时候,Pods/Headers/Public 和 Pods/Headers/Private 的内容一样且会包含所有头文件。

这里的主要问题是1:

就是在 Static Library 的状况下,一旦我们开启了 Use Header Map,结合组件里所有头文件的类型为 Project 的情况,这个 hmap 里只会包含 #import "ClassA.h" 的键值引用,也就是说只有 #import "ClassA.h" 的方式才会命中 hmap 的策略,否则都将通过 Header Search Path 寻找其相关路径,这样开启 Use Header Map 并不会提升编译速度 (使用 Framework 的情况就没问题)

我们知道在引用其他组件的时候,通常都会采用 #import <A/A.h> 的方式引入。至于为什么会用这种方式,一方面是这种写法会明确头文件的由来,避免问题,另一方面也是这种方式可以让我们在是否开启 clang module 中随意切换。当然,还有一点就是Apple 在 WWDC 里曾经不止一次建议开发者使用这种方式来引入头文件。

所以说在引入组件比较多的情况下势必导致编译速度过慢了。

美团方案

美团在今年初发布了他们以 Header Map 技术 为基础自研出的一款 cocoapods 插件cocoapods-hmap-prebuilt,来进一步提升代码的编译速度,完善头文件的搜索机制。

据说cocoapods-hmap-prebuilt 插件能将总链路提升 45% 以上的速度,在 Xcode 打包环节上能提升 50% 以上的速度,当然这个效率的提升是建立在美团几百+数量庞大的组件基础上的,hmap也只对组件化项目效果明显。

大概看了文章介绍理解下来感觉是写了一个脚本去遍历项目以及三方库的头文件,将所有头文件提取出来后,用上面的方法生成一个hmap,其中重点是做了组件名/头文件名方式的 Key-Value 的优化,重写 xcconfig 文件里的 Header Search Path 到对应的 hmap 文件上,并将项目中组件自身的 Ues Header Map 功能关闭,减少了不必要的文件创建和读取。


以上就是所有关于Hmap的内容啦,希望有帮助到大家进一步了解头文件的编译优化!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容