使用include-what-you-use优化编译速度

include-what-you-use(以下简称IWYU)是Google推出的用来检查头文件冗余的工具。原理是分辨出#includes里有哪些是当前文件(.cpp,.cc,.h)完全没有用到的,以及使用前置声明来替代#includes。线上的大型项目中经过一段时间的线上开发过程,经常会有间接引用的情况,例如a.h实际是需要include c.h,但是已经include b.h中已经include c.h了,这样虽然编译能通过,但是增加了耦合,不利于后续的维护。

IWYU的好处

  • 更快的编译。 当文件包含冗余的头文件时,编译器会读取,预处理和解析更多的代码。如果存在模板,则会引入更多的代码,加大编译时间。 使用前置声明代替include语句,也能减少依赖,减少可执行程序的大小。
  • 更好的重构。 间接引用的存在会导致去除include时,编译失败。不利于解耦。
  • 头文件自注释。可通过查看头文件注释,知道该功能依赖其他哪些子功能。

include-what-you-use是使用clang分析符号的引用,每个clang版本更新时,iwyu都会有对应的改动。如果开发环境下已经安装了clang的环境,那么需要对照版本下载对应的iwyu的版本。下面是官网(https://include-what-you-use.org/) 提供的版本对应表。github的地址:https://github.com/include-what-you-use/include-what-you-use

ClangIWYU versionIWYU branch
3.60.4clang_3.6
3.70.5clang_3.7
3.80.6clang_3.8
3.90.7clang_3.9
4.00.8clang_4.0-r2
5.00.9clang_5.0
60.10clang_6.0
70.11clang_7.0
80.12clang_8.0
90.13clang_9.0
100.14clang_10
110.15clang_11
.........
master master

 

如果项目没有clang环境,那么首先要安装llvm框架系统,然后选择对应的iwyu安装。这里不介绍llvm的安装方法了。

IWYU安装

下载好iwyu源码后。解压到目录下iwyu下。使用cmake指定好llvm的路径,和源代码的路径,编译项目。

#拉取IWYU源码
iwyu$ git clone https://github.com/include-what-you-use/include-what-you-use.git

#进入目录 切换到llvm版本对应的IWYU分支
iwyu$ cd include-what-you-use
iwyu/include-what-you-use$ git checkout clang_11 

#返回上级目录 准备cmake相关
iwyu/include-what-you-use$ cd ..
iwyu$ mkdir build && cd build

#IWYU 0.10/CLANG 6 或者更早的版本
iwyu/build$ cmake -G "Unix Makefiles" -DIWYU_LLVM_ROOT_PATH=/usr/lib/llvm-6.0 ../include-what-you-use

#IWYU 0.11/CLANG 7 或者更新的版本
iwyu/build$ cmake -G "Unix Makefiles" -DCMAKE_PREFIX_PATH=/usr/lib/llvm-7 ../include-what-you-use

#cmake生成好编译文件后
iwyu/build$ make -j8

#安装iwyu
iwyu/build$ sudo make install

安装完成后,默认是安装到了/usr/local/bin/目录下。

IWYU使用

编译项目时,如果是使用Makefile,插入-k CXX=/path/include-what-you-use即可。

下面是一个使用Makefile的简单例子

test_sample.cpp

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <iostream>

int main(int argc, char* argv[])
{
	std::string tmpstr = "test";
	std::cout << tmpstr << std::endl;
	return 0;
}

Makefile

CXX=/usr/bin/clang++

TARGET = test
OBJS = test_sample.o

$(TARGET) : $(OBJS)
	@rm -rf $(TARGET)
	@$(CXX) $(OBJS) -o $@ 

clean:
	@rm -rf $(TARGET) $(OBJS)

.PHONY: clean

直接输入

$ make -k CXX=/usr/local/bin/include-what-you-use

输出结果

test_sample.cpp should add these lines:
#include <string>    // for operator<<, string

test_sample.cpp should remove these lines:
- #include <math.h>  // lines 3-3
- #include <stdio.h>  // lines 1-1
- #include <string.h>  // lines 2-2

The full include-list for test_sample.cpp:
#include <iostream>  // for endl, basic_ostream, cout, ostream
#include <string>    // for operator<<, string
---
make: *** [<builtin>: test_sample.o] Error 6
make: Target 'test' not remade because of errors.

可以看到IWYU提供了include的一些意见,可以把输出导入到iwyu.out文件中,使用fix_includes.py修改对应代码。

$ make -k CXX=/usr/local/bin/include-what-you-use 2> iwyu.out
$ python /usr/local/bin/fix_includes.py < iwyu.out

修改完成后的代码

#include <iostream>
#include <string>

int main(int argc, char* argv[])
{
	std::string tmpstr = "test";
	std::cout << tmpstr << std::endl;
	return 0;
}

可以看到includes已经被自动应用修改了。

fix_includes.py --help可以查看一些额外的参数,例如默认是不会移除没有使用的includes,加上--nosafe_headers,可以在.h头文件中移除iwyu认为多余的includes。 --comments可以给includes加上注释,表明用到了其中的哪个模块。

如果使用的是cmake,在build目录下

mkdir build && cd build
CC="clang" CXX="clang++" cmake -DCMAKE_CXX_INCLUDE_WHAT_YOU_USE="path/bin/include-what-you-use"
make -j8

 

实际项目使用

在项目中实际使用中,因为项目是使用make编译生成多个进程的,实际优化中是先删除某个进程的编译产生的文件,对不同进程分别使用iwyu。

src$ rm bin/GatewayServer && rm -rf .obj/GatewayServer/

src$ make GatewayServer -j32 -k CXX=/thirdpart/bin/include-what-you-use 2> iwyu_gate.out

src$ cd GatewayServer

src/GatewayServer$ python /thirdpart/bin/fix_includes.py --nosafe_headers < ../iwyu_gate.out

应用完修改后,在对GatewayServer重新编译,因为带了--nosafe_headers,有些间接引用会导致编译无法通过,需要自己手动修改。修改完成后,使用du命令查看makefile生成的中间文件大小。 .obj/GatewayServer/的大小从32248kb缩小到了32192kb,减少了50kb左右的大小。使用/usr/bin/time对比单线程的 前后的编译时间差距,User Time从90.37s缩短到了88.94s,System Time从8.99s缩短到了8.74s。Real Time从99.35s缩短到97.68s,测试结果说明实际项目中,IWYU是有效果的,但是整体优化效果只能说是一般。整体编译优化上,整个服务器所有进程在使用了多线程编译后,全量编译下,make -j32,测试下来从8m12s缩短到了7min59s。

项目中的另外两个进程Scene和Function使用了Unity Builds,将.cpp文件包含在了多个NXProjectUtil.cpp文件中,使用IWYU分析这部分代码时发现由于使用了Unity Builds,IWYU不能正常工作。修改了Makefile,删除NXProjectUtil.cpp,改为对所有文件编译后,发现由于之前一直是使用Unity Builds导致了很多cpp文件并没有include所需要的头文件,出现了一大堆编译报错。只好对着一堆编译报错慢慢修复,修复完成后再使用IWYU分析代码,发现可以正常工作了。使用IWYU修改完代码后,在回退掉Makefile,恢复移除的NXProjectUtil.cpp,完成了Unity Builds的IWYU代码分析。

Unity Builds是将项目中的所有源代码include到了一个文件,对这个文件编译即可,实际应用中考虑到make是可以多任务执行的,一般是依据模块分成多个文件,由于是对unity.cpp进行编译,可以预见到IWYU对include优化能起到的作用有限,但是长久的维护Unity Builds的项目会导致大量的本应该include的,没有加进去,后续在做IWYU优化十分痛苦。但是也并不是完全没有必要,一般的项目用到Unity Builds还是会分成多个unity文件,这时候对不同unity文件间做解耦还是有必要的。尤其是对后面如果要做重构工作,会轻松很多。

IWYU在使用前置声明替代include时,发现对项目中使用的proto,进行了大量替换,将proto生成的.pb.h文件在.h中全部替换成了前置声明,这里可以预见到对于修改proto导致的增量编译,能起到很好的优化效果。这里测试了修改某个proto文件,进行了一次增量编译测试,使用make -j8。 User Time从1515.30s缩减到1392.49s,System Time从81.83s缩减到76.72s,Real Time从4min46s缩减到4min31s。在增量编译中,可以观察到修改了proto,各个进程的链接过程都是要执行的。但是使用了前置声明的部分,在执行编译时,有部分已经解耦的文件不再做增量编译了,减少了增量编译的时间,可见对日常开发中还是有一定作用的。

参考文档:

https://www.cnblogs.com/cherishui/p/12860452.html

https://github.com/include-what-you-use/include-what-you-use/blob/master/README.md

https://include-what-you-use.org/

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值