我们有一个梦想,让每一名研发工程师拥有一台“超级”计算机。
作者:字节跳动终端技术——孙雄
大型工程的效率瓶颈
近年来,基于Devops流水线的研发流安全教育平台登录程,逐渐成为软件研发的行前端业标准。流水线的运行效率,决定了团队的研发效能。对大型项目来说,编译构建往往是流水线中耗时占比的大头。有些工程的编译时长超过30分钟,甚至达到几个小时。这样的性能,是非常糟糕的。
字节iOS大型项目的构建时长,大多控制在5分钟以内。这主要得益于内部的编译加速解决方案,它集分布安全生产法式编译和分布式缓存为一体,本文将详细介绍它安全生产法的工作原理。不过在这之前,我们先来分析一下大型项目的编译瓶颈和解决思路。
先说结论,机器性能不足和前端重复作业,是影响工程编译效率的两个最大因素,对此,可以采取分布式编译+编译缓存的方式,提升整体的缓存视频变成本地视频性能。
分布式编译
工程的编译,往往可以拆解为可缓存视频变成本地视频并行的编译子任务。以C系列语言(C
, C++
, ObjC
)为例,项目中往往存在上千甚至上万的源代码文件(以 .c
, .cc
或 .m
作为扩展名的文件),每个编译子任务将源代码文件编译为目标文件(以 .o
作为扩展名的文件),再整体链接成最终的可前端工程师执行文件。
这些编译子任务可以并行执行,如下图所示:
CPU的数量,决定了编译的并行度上限。个人电脑(PC)的android下载安装CPU核心数通常在4~12之间,专用服务器可以达到24~96,但对于动辄上万文缓存件的大型工程安全中心,CPU的数量还是显得不足。这时前端学什么候,利用分布式编译的技术,可以得到产品策略一台“超级计算机”。
编译缓存
大型工程全量编译,需要处理几千甚至前端是什么工作几万个编译子任务。但大多数子任务,之前已经编译过,如果我们能通过某种方式,直接获取编译产物,就可以大大节省时间。
建立一个中央仓库,存储编译子任务的产物,这些产物可以通过“任务摘要”来索引。这样每次遇到一个新任务,我们首先向中央仓库查询摘要,如果查询成功,直接下载编译产物,就省去了重复编译的动作。
上面提到的分布式编译和编译缓存,是提升大型项目编译效率的两产品生命周期大法宝,本文主要介绍字节跳动的分布式编译解决方案。
“超级”计算机
借助云计算,我缓存视频怎样转入相册们可以以组装的方式,得到一台“超级”计算机,如下图所示前端面试题:
这台“超级”计算机,缓存的视频怎么保存到本地由一台中安全中心心节点和若干台工作节点前端学什么组成。中心安全生产法节点负责生成和调度编前端译子任务,依照它们的执缓存文件夹名称行顺序,将任务发送给空闲的工作节点来执行。这样整个系统的并行处理能力,取决于所有工作节点的CPU之和,性能比单机高出数倍,甚至数十倍。
像这样把任务分发给工作节点的方案,又称为分布式编译。分布式编译并不是新鲜的概念,2008年开源的distcc工具就提供了分布式编译的解决方案。Goo安全gle在缓存清理2017年提出的Remoandroid是什么系统te Execution API,又从协前端和后端议的角度规范了分布式编译和编译缓存的实现方式。
我们先android下载看一下分布式编译的核心思安全路。
核心思android的drawable类路
核android/yunos心思路很简单,本地计算出编译命令需要读的文件,把文件列表和编译命令,发给远端机器,执行编译命令。编译结束后,再请求拉取编译产物。
其前端开发需要学什么中,如何找到所需文件是关键。
背景知识——预处理缓存清理
在介绍我们的做法之前,需要先补充一Android些编译原理相关的背景知识。
待编译的源文件,可以通过#include xx.h
和 #import xx.h
的方式,声明对某头文件的依赖。
编译器处理编译命令前端学什么的第一阶段叫做“预处理”,该阶段的一个重要工作是头文件展开。假设入口文件main.m
中有一行为#import Car.h
,编译器会遍历所有安全模式怎么解除搜索路径,找到Car.h
文件,并读取该文件内容,替换掉main.m
中的#import Car.h
行。其中搜索路径由编译命令中的 -I
, -isystem
等参数给出
接下来,如果 Car.h
文件中有 #import
语句缓存视频怎样转入本地视频,编译器会重复上述动作,找到依赖的文件,读取内容,进行替换,直到把所有的 #import
语句全部展开。
因此,假设我们模拟预处理的过程,找到所有依赖的产品批号是生产日期吗头文件,就可以将该任务发送到远端执行。
重要引擎
由上述编译原理可知,依赖分析是实现分布式的前提。不仅如此,依赖分析也是性能的决定因素。
由于依赖分析只能在本地进行,计算资源是有限的。依赖分析的性能,决定了任务分发是否流畅,如果依赖分析过慢,会导缓存文件夹名称致大量工作节点限制,任缓存的视频怎么保存到本地务分发出现瓶颈。
可以把依赖分析,理解为分布式编译的重要引擎。
依赖分析的实现并不复杂,编译器本身就提供了相关参数,以clang
为例。-M
可以获取完整的编译依赖,而 -MM
则可以得到用户定义的依赖,相关参数解析如前端和后端下:
-M
,
--dependencies
Like -MD, but also implies -E and writes to stdout by default
-MD
,
--write-dependencies
Write a depfile contai产品批号是生产日期吗niandroid平板电脑价格ng user and system headers
-MM
,
--user-dependencies
Like -MMD, but also implies -E and writes to stdout by default缓存文件夹名称
-MMD
,
--write-us产品运营er-depen缓存是什么意思dencies
Write a depfile candroid平板电脑价格ontaining us前端开发需要学什么er headers
开源框架recc
直接使用了编译器能力。
这种方法的好处是开发简单,并且足够安全,产品但性能存在瓶颈。我们早期以头条项目测试的时候,通过编译器获取依赖,平均耗时在200毫秒左右。而单个文件的编译时长,大多在500毫秒~3000毫秒的区间内。依赖分析耗时占比太高,导致任安全教育平台登录入口务分发效率不够理想。
依赖分析时间过长,安全教育平台登录一方面由于编译器命令由独立进程执行缓存是什么意思,不同的编译任务之间无法复用缓存。另一方面,编译器产品的前端工程师 -M
参数隐含了参数 -产品E
,后者代表“预产品运营处理”,预处理阶段除了依赖分析,还做了不少其它工作,这部分工作我们产品设计可以优化掉。
Google的 goma
采用了自研的依赖分析模块,并且在Chromium和Android这两个大型项目上取得了非常好的结果。它在实现依产品设计专业赖分析的时前端和后端哪个工资高候,借助常驻进程的架构优势,运用了大量缓存,索引等技巧,提高了中间数据的复用率。
在使用 goma安全教育平台登录入口
加速内部iO安全期计算器S的项目的过程中,我们发现当编译任务依赖的Framework过多,或者前端依赖的hmap文件过大的情况下,性能产品质量法会受到较大影响,于是,我们针对大型iOS项目的特点,在goma
基础上进行了优化,最终可以以平均50ms的速度,完成编译任务依赖解析。
接下来,让我们一起看看g安全oma
在设计时运用了哪些技巧,以及我们针对iOS项目做了哪些优化。由于篇幅有限,本文只介绍比较有代表性的部分。缓存视频合并app下载
快速依赖分析
goma采用了依赖缓存和依赖分析结合的方案,如果之前在工作目录下进行过编译,下次使用时,可以直接使用安全中心依赖缓前端存,只有在缓缓存视频合并app下载存不命中的情况下,才进行依赖分析。
依赖缓存
依赖缓存的核心原理是:检查相同编译参数对应的缓存视频在手机哪里找,上一次的依赖,如果依赖的文件都没变,即复用依赖关系。
其流程如下图所示:
有人可能会有缓存视频变成本地视频疑问,为什么可以检查上一次的依赖?如果这次引入了列表外的android手机新文件,岂不是无android手机法判断文件是否改变吗。
其实不然,引入新文件的前提是加入了新的#import
指令,它必然导致旧依赖列表中的某个文件发生改变,因此这种做法是相对安全的。
命中依赖缓存的话,可以在5毫秒以内得到编译命令的依赖文件列表,这是一个很理想的性能。
不过在实践中经常发现,即使文件修改了,依赖关系也大多是不变的,例如修android下载安装改变量的值或增加一个类成员。如果我们能抓住这个特性,就可以大大增加缓存命中前端率。
忽略无关行
有些代码修改影响依赖,有些则不会,如果我们只考虑影响依赖的改动,就可以排除掉大量干扰因素。下面是两个例子,展示了有效改动和无效缓存清理改动的区别。
- 有效改动(导致依赖分析缓存失效)
- #include <foo.h>
+ #include <bar.h>
- 无效改动 (不影响依赖分析缓存)
- int a = 2;
+ int a = 3;
除了前文提及的#include
和 #import
,还有如下语句可能造成缓存失效:#if
, #else
, #define
, #ifdef
, #ifndef
, #include_next
。
它们的共性是以#
开安全工程师头,在预处理阶段会被编译器解析。这些指产品设计专业令统称为Directive
,因此,我们只需缓存文件的Directive
列表,当文件内容安全模式怎么解除发生改变时,重新获取Direcitive缓存清理
列表,并和之前缓存的内容对比,如果列表不变,就缓存视频变成本地视频可以认为该文件的改动不影响依赖关系。
依赖分析
深度优先分析
如果没命中依赖缓存或者关闭了该功能,就会进入依赖分析的阶段。
依赖分析采用深度优先搜索的算法,找到代码中所有的 #include
和 #import
对应的文件。需安全期计算器要注意的是,#if
和#else
这样的条件宏,也需要在预处理阶段解析。
深度优先采用文件栈 + 行指针的方式实现,前端和后端哪个工资高如图所示:
图中紫色部分是一个文件栈,栈中每前端开发一个元素都存放了文件相关的信息。每android电子市场一个文件都对应一个Directive
(预处理指令)列表,并维护一个指针,指向当前的Directive
。
流程开始阶段,入口文件进栈,随后遍历入口文件的所有Directive
,当读到 #include
或 #import
相关的 Directive
时,搜索依赖文件,并入android下载栈。
此时,虽然入口文件还没有解析完,但按照规则应该优先解析新入栈的文件,android手机所以需要通过指针维护入口文件当前读到的行号,以保证下次回到入口文件时,可以继续向前端电视剧下解析。
优化技巧
依赖分析的过程中,存在大量重复的操作,可以通过很多小技巧来优化这个过程。本安全教育平台登录文将介绍两个比较典型的小技巧。
倒排索引
依赖分析中最常见的操作在一堆备选目录中,找到对应名称的文件。
假设我们需要找到#imporandroid电子市场t <A/A.h>
语句中提到的A.h
文件。命令行中有前端面试题10个-I
参数,分前端学什么别指向10个不同的目录-Ifoo, -Ibar, ...
,最朴素的方法是依缓存清理次遍历这前端和后端哪个工资高10个目录,拼接路径,尝试找到A.h
文件。
这种方法当然可行,但是前端面试题效率较低。对于大型项目,仅一条编译命令就可能涉及超过5000条#import
语句,和超过50个头文件搜索路径。这意味着至少5000*50=25万次文件系统查找,时间开销非常大。
建立倒排索引,可以大大加快这个过程。其思路是预先遍历待搜索目录(directory
),找到目录下的文件和子目录(统安全教育称entry产品领域的偏好
),然后建立entry
指向directory
的倒排缓存文件夹名称索引, 如下图所示:
回到上面的问题,当我们搜索#import &android的drawable类lt;A/A.h>
时,首先需要找到foo
, bar
, taz
三个目录里,哪个含有A
子目录,根据倒排索引,可以快速定位产品生命周期到bar
目录,而不需要从头开始遍历。
值得注意的是,objc工程普遍采用HeaderMap技术(即Xcode自动产品密钥生成的.hmap
文件),提升编译时查找头文件的效率。HeaderMap本质上也是一种索引安全中心表,它建立了 Dire前端学什么ctive -> Pa缓存的视频怎么保存到本地th 的直接映射关系。我们在建倒排索引的时候,需要解析.hmap
中的内容,并合安全中心并到倒排索引中。
跨任务缓存(针对iOS项目的优化)
不同的编译任务,可能存在相同安全工程师的依赖文件。例如foo.m
和bar.m
可能都依赖了common.h
文件,编译f缓存视频怎样转入相册oo.m
的时候已经找到了common.h
, 编译bar.m
的时候,是否不需要再找一次了呢?
很遗憾,大多缓存文件夹名称数情况需要重新查找,因为不同命令产品经理的查找条件往往不一样。影响查找条前端和后端件的参数有很多,例如-I
, -isystem
影响头文件搜索路径,-F
影响Framework搜索路径。
不过,iOS项目往往可以复前端和后端用之前的查找结果。
iOS项目通常采用Xcode安全教育平台登录 + CocoaPod缓存文件夹名称s的研发模式,针对同一个Pod内源文件的编译命令,头文件搜索路径基本是一致的。利用这个特性,我们提供了跨任务的缓存加速方案。产品设计专业
我们对搜索路径列表整体做产品密钥了一层has安全教育平台登录入口h,当两个命令的搜索路径相同时,对同名Directive的搜索结果一定相同。方案如下所示:
- 在对单条命令进行依赖解析之前,先提取搜产品质量法索路径的特征值。
- 寻找头文件时,先查询缓存,前端工程师如果查不到,在找到头文件后,将结果缓存。
举一个具体的例子:
编译任务1:
clang`` -c ``foo.m`` -IFoo -IBar -FCar
编译任前端工程师务2:
clang`` -c ``bar缓存是什么意思.m`` -IF安全期计算器oo -IBar -FCar
foo.m
和bar.m
均包含行:#import common.h
假设编译任务1先执行,我们的做法应该是:
- 提取前端电视剧搜索目录列表为:
-IFoo -IBar -FCar
- 使用SHA-256算法计算摘要,对安全期计算器应的搜索摘要为:598cf1e…(仅展示前8位)
- 进行依赖分析,读到
foo.m
依赖common.h
的部分,遍历搜索目录产品领域的偏好,找到common.h
的位置,假设在目录Bar
下面。
- 写缓存,缓存用哈希表实现,key为
<598cf1e..., common.h&g产品生命周期t;缓存视频合并
,value为Baandroid平板电脑价格r
- 执行android手机编译任务2,再次遇到寻找
common.h
的请求。
- 直接从缓存中查到
common.h
在产品策略Bar
目录下
索引缓存(针对iOS项目的优产品密钥化)
建索引可以减少遍历目录寻找头文件的次数,是非常有效的优化方案。但是当头文件搜索目录过多,或者hmap过大前端开发时,建索引本身也需要几前端和后端十毫秒的时间,对于性产品设计能要求十分严苛的依赖解析来说,这个时间还是略长。
所以我们想到,对索引本身是否可以做缓存呢?
按照跨任务缓安全教育平台登录存的思路,索引本身也是可以缓存的,只要两个任务的头文件搜索路径,以及hmap中的索引内容都一直,它们就可以共用一套索引。
具体的方案和跨任务缓存类似,本文就不详细展开了。通过对索引的缓存,我们将依赖分析的速度又提升了20毫秒左右。
总结
分布式编译和编译缓存是提升大型项目编译效率的两大法宝。本文主要介绍了字节跳动的分布式编译解决方案。
该方案核心部分采用了开源框架goma的代码,并在此基础上,针对iOS项目的特性,做了一定的优化。
分布式编译的核心思想产品领域的偏好是空间换时间,引入额外的机器,提升单次编译的CPU数量。分布式编译的效果,取决于中心节点分发任务的速度,任务的分发又取决于依赖的解析效率。
传统方案利用编译器的预处理来解析依赖,方法可行,但由于每次解析都要单独fork产品运营进程,数据难以复缓存的视频怎么保存到本地用,存在性能瓶颈。我们采用了开源框架goma的代码,前端并在此基础上,针对iOS项目的特性,做了一定的安全中心优化。
本文介绍了依赖解析的安全四种技巧,分别从消除噪音,索引,缓存三个角度进行了优化。编译优前端开发化的道路,任重而道远。感谢goma团队,提供了缓存视频合并许多优秀的设计思路和技巧,我们也会在此方向持续研究,尽可能的把思路分享给大家。
关于字节终端技术团队
字节跳动终端技术团队(Client Infrastructure)是大前端基础技术的全球化研发团队(分别在北京、上海、杭州、缓存视频合并深圳、广州、新加坡和美国山景城设有研发团队),负责整个字节跳动的大前端基础设施建设,提升公司全产品线的性能、稳定性和工程效率;支持的产品包括但缓存不限于抖安全中心音、今产品领域的偏好日头条、西瓜视频、前端开发需要学什么飞书、番茄小说等,在移动端、Web、Desktop等各终端都有深入研究。
就是现在!客户端/前端/服务端/端安全智能算法/测试开发面向全球范围招聘 ! 一起来用技术改变世界,感兴趣请联系bits-dev-better@by安全教育平台登录tedance.com。邮件主题:简历-姓名-求职意向-期望城市-电话。
MARS- T安全ALK 04 期来啦!
2月android/yunos24日晚 MA产品RS TALK 直播间,产品经理我们邀请了火山引擎 APMPlus 和美篇的产品质量法研发工程师,在线为大家分享「APMPlus 基于 Hprof 文件的 Java OOM 归因方案」及「美篇基于MARS-APMPlus 性能监控工具的优化实践」等技术干产品质量法货。现在报名加入活动群 还有机会获得最新版****VR一体机——Pico Neo3哦!
⏰ 直播时间:2月24日(周四) 20:00-21:30
活动形式:线上直播
报名方式:私信小编获得进群二维码
作为开年首期MARS TALK,本次我们为大家准备了丰厚的奖品。除产品批号是生产日期吗了Pico Neo3之外,还有罗技M720蓝牙鼠标、筋膜枪及字节周边礼品等你来拿。千万不要错过哟!
点击这里,了解APMPlus