编译构建时间优化

背景

项目是一个Windows桌面应用程序,用VS2010开发,后台是C++开发的DLL,UI是C#,目前已有103万行代码。
在代码只有30万行的时候,编译时间大约是10多分钟,反馈的速度足够快。然而随着代码膨胀,编译时间急剧上涨,当前的编译构建时间高达120分钟,也就是说,当你提交了一行代码,需要等120分钟之后,才能做全量测试。
如此长的等待时间,绝对是在浪费生命,此前我就一直想要优化,奈何见效不大。
原因有二:

  • 构建的机器太差了,4核4G的虚拟机,蜗牛速度。
  • 项目中各组件(DLL)用的是隐式记载的方式。如果A依赖B,那么,必须等待B编译结束,才能编译A,只能串行。

我向领导安利了多次,采购一台好点的服务器,用来编译构建。也算了这么一笔账,项目组有100多人,如果有100个人在等待编译的话,这100个人的一小时的工资可不是小数目啊,绝对比一台服务器贵了。然而还是。。。。。。
还向各开发组安利过,快速构建的好处,可以减少等待,缩短反馈周期,更快的进行测试验证,然而。。。。。
对此我想说,呵呵!
不能再吐槽了,还是讲正事吧。

由于家里没有windows系统,无法截图,只能凭记忆胡乱记录一下。

分析各组件的依赖关系

既然无人支持,但我还是得试试。
于是顺手写了个脚本分析组件之间的依赖关系,并且看C++代码,将项目整体分为4层。UI是C#工程,用显式加载的方式调用C++,故可以独立出来。
脚本http://blog.decbug.com/2015/07/26/pe_depen/

名称
framework
midware
plugin
名称
UI

下层依赖上层,也就是下层必须等待上层编译完成。同一层的组件相互独立,可以并行构建。

分层之后,编译还是那么滴慢,毕竟CPU内存资源有限,再优化的意义也不大。

升级服务器

正当我无可奈何之际,公司出了这么一个要求:让每个项目的编译构建时间小于15分钟,这时候大家才重视起来。。各种资源刷刷滴都来了。

时间还增加了?

有了领导的重视,事情就好办了,服务器刷刷的就到位了,24核96G,还有20T的硬盘,看着都爽啊。
高高兴兴的把构建脚本拷贝过去,然后自动下代码,开始编译。
原本以为在硬件有极大提升的情况下,构建时间应该会缩短一半多,达到50分钟的水平。
可实际情况却让我无比郁闷,竟然时间还增加了。想起之前在领导面前夸下的海口,说有办法搞到25分钟左右,领导才同意申请服务器的。

分析并解决

只好开始新一轮的构建,手动更新一下代码,发现更新速度超级慢,猜测是网络的原因,导致下载很慢,经过分析,网络正常。
内存和CPU都极其给力,自然不会是瓶颈。于是把怀疑目标转向硬盘,用crystal mark测试一下硬盘速度,果然奇慢无比。看来真的是硬盘拖累了速度啊。
远程开机,查看bios中关于磁盘的设置,竟然是RAID5。。。。
好吧,反正是编译机器,不需要数据安全,那就改成不raid吧,再次编译,时间减少到了60分钟。

分析VC的工程文件

减少文件拷贝

sln:其实就是一个配置文件,把用到VC工程即vcxproj都include进来
vcxproj其实就是xml文件,记录着cpp h的相对路径,各种编译选项等等。可以把它当成xml进行读写。

查看每个vcxproj,发现prebuild和postbuild的event都会做拷贝文件的操作,大约会拷贝近300M的文件。看了下拷贝路径,原来是用于本地调试的,对于编译构建来说,完全用不着。
写个powershell脚本,在编译的ant脚本里调用一下,每次构建之前都调用一次,把这两个event都disable掉。
经过测试,时间从60分钟降低到50分钟

开启mp

观察编译过程,CPU利用率始终上不去,没有发挥多核的优势啊,得想办法把CPU都利用起来。
看了下编译选项,有个/MP引起了我的注意,看看微软咋说的

1
2
/MP (Build with Multiple Processes)
The /MP compiler option can significantly reduce build time when you compile many files.

我那可是24核的机器啊,不开启多核编译真的是太浪费资源了。
老规矩,windows平台,写个powershell脚本,遍历所有工程的vcxproj文件,把/MP都打开。

在ant编译脚本里加入套餐,在编译之前执行powershell脚本,再次测试,时间降低到40分钟。

分布式编译

继续观察编译过程,大部分时间的CPU都能有90%以上,但有一段时间内CPU始终只有10%左右。打开任务管理器,选择列,命令行。看下MSBUILD进程的命令行,找出此时是在搞哪个SLN,想办法把这个SLN的时间降下来。
找到SLN后,查看它的代码结构,发现优化的空间不大。只好用别的方法,由于link的原因,每个SLN的编译过程,都是串行的,所以CPU一直上不去。
找incredi build,可以并行link,充分利用CPU,修改编译脚本,在编译这个SLN的时候,不用MSBUILD,改incredi build,CPU果然上去了。
经测试,整体的时间从40分钟降低到30分钟

这不是终点

虽然用到incredi build,但这只是伪分布式,所有的计算资源都是在一台服务器上,当代码继续膨胀,编译时间还是会变长。
这只是在治标不治本,真正要做到分布式构建,每个组件都能并行构建的话。需要把隐式加载改为显式加载,即组件之间只通过头文件进行依赖,而不是通过lib,这样就不用等待上游的完成。

具体方法是:

  • 都改成loadlibrary,GetFunctionProcess的方式加载组件,组件之间在编译过程中相互独立。
  • 如此便可把将每个组件的编译过程推送到agent上执行
  • 汇总每个agent上的编译结果(DLL)

这样整体的编译构建时间,就是编译最慢的那个组件的时间了。

然而我已换部门,只能先把demo弄好,等待后来者完善了。


本博客欢迎转发,但请保留原作者信息
github:codejuan
博客地址:http://blog.decbug.com/