GCC编译优化应用预编译头
服务器编译优化记录
对项目编译优化过程中一些思路和脚本工具实现。对内存受限的编译环境有一些帮助。
工具:
https://github.com/wangxiaobai-dd/GccPrecompiledHeader
环境
32G内存,16核,Makefile,gcc9.2
效果
选择了代码比较多的三个目录进行预编译头优化, 进行下述操作。另外: 将小的编译单元合并成大的编译单元也是有效的,如 com.cpp 包含 a.cpp b.cpp , 在com.cpp 中首行也需要 #include "inc.h" ),编译时间从 24min30s左右 降到 14min 左右 ,节省 40%以上编译时间
方法
1. 梳理编译单元
编译时,我们每一个cpp文件将会生成对应的一个.o目标单元,将小的编译单元合并成大的编译单元对编译速度会有显著的提升(已有)。
类似上图,用n.cpp 包含 a.cpp,b.cpp,用m.cpp包含c.cpp,d.cpp,将 4 个编译单元变成 2 个编译单元。
注意,这里我认为应该将修改频率低的文件进行编译单元合并,如果将高修改频率的cpp文件放入合并后的编译单元,那么每次修改这个文件都会导致包含这个大编译单元重新编译。
另外,合并的编译单元尽可能地有一定关联度,可以按照业务功能来划分,或者通过一些数据统计来划分。
2. 应用gcc预编译头
按照图2,当a,cpp b.cpp c.cpp都包含temp.h头文件时,在编译的时候,temp.h会被解析三遍,分别与a,b,c结合生成.o文件。这样我们可以预先对头文件进行编译,生成temp.h.gch文件,这样只需要解析一次temp.h,原来的a.cpp,b.cpp,c.cpp文件仍然include temp.h就好了,只是要最先包含temp.h。
编译时 -H 可以看到cpp文件的依赖信息,应用gch后 !temp.h.gch
在应用gch时,我的方法是:
- 先使用了include-what-you-use 这个开源工具,去除重复包含的头文件,前置声明替换(也是一种优化)
- 分析路径下头文件的包含频率
- 筛选包含频率高但是改动频率小的头文件,放在新的inc.h文件中
- 剔除cpp文件包含的上一步的头文件,并在cpp中首行插入#include "inc.h"
- 修改Makefile,修改依赖关系,编译时先对inc.h进行编译,生成最新的inc.h.gch
- Makefile 可以加入 -Winvalid-pch,inc.h.gch 不可用时编译报错
工具
从上面第二步开始 ,以我的仓库里的代码举例 ,GchTool/TestTool/dirA.bak 是预编译头优化前, dirA 是优化后 ,大家可以对比参考。
一些脚本说明:
- 分析路径下头文件包含频率: CheckInclude.cpp
使用: ./CheckInclude TestTool/dirA ,
生成 analyseInc.txt , 供 ReplaceGCH.sh 脚本使用:
analyseInc2.txt ,方便我们自己查看头文件包含频率 :
- 可以根据 git 或者 svn 提交记录,手动从 analyseInc.txt 剔除改动频率大的头文件,(头文件包含应是递归包含的,当时没有考虑到嵌套问题)
- 使用预编译头替换脚本 ReplaceGch.sh
使用:./ReplaceGCH.sh TestTool/dirA/ 3 取 analyseInc.txt 前 3 头文件,对 dirA 路径下,将这些头文件从 cpp 文件中删除,新建文件 inc.h 用于包含这些头文件,并且将 #include "inc.h" 插入刚才修改的 cpp 文件的首行
要处理的头文件数量,根据我们的实际项目规模来确定,这里举例是 3
向受到影响的 cpp 文件 插入 inc.h ,会调用 InsertInc 脚本:
自动创建 inc.h :
- 修改 Makefile
修改 dirA/Makefile 依赖,保证每次编译先对 inc.h 进行编译 生成 inc.h.gch
修改 项目Makefile,支持多核编译(目录间多核,比如 dirA 对 inc.h 编译生成 inc.h.gch 时,其他目录仍然在编译):
这里 targetA 是公共库目录 ,targetB、targetC 都要依赖 targetA,如 targetA 生成的静态库
对 inc.h 包含的头文件递归检查,刷新 inc.h 的时间戳,促使生成新的 inc.h.gch (比如inc.h 包含 common.h ,我们对 common.h 修改,gcc 并不认为 inc.h 有改动,因此我编写了脚本 gchcheck.py 做编译前检查):
过程输出
进行编译优化后,发现在多核编译中, 目录A产生编译错误时,目录B不会停下来编译,会将错误信息刷屏,我们需要花很多时间向上滚屏翻记录,十分不友好;另外冗余的编译信息(如编译参数 链接参数 都可以在 Makefile 中查看)对我们用处不大。
于是编写脚本,在编译时收集编译信息,友好的展示出来: compliedisplay.py
代码与使用示例:
旧的编译信息输出:
修改后:
使用方法:
- 修改项目 Makefile
红框:调用脚本,我放在了targetA中调用,后台会fork子进程
绿框:ENTRY = ‘$@file’、DIR=$(DIRB)、 2>>$@error 将信息传给编译的子目录,编译信息会写入文件 targetAfile,错误信息写入文件 targetAerror , 对于targetB 则是 targetBfile targetBerror
黄框:表示 target 编译结束
- 修改子目录下 Makefile ,举例 dirA/Makefile
红框:开始编译文件
绿框:开始链接
注意在这些命令前加上@, 在 $(CXX) xxxx 前也加上,这样就不会打印这条执行语句了。
小结
现在3.16的cmake,可以对预编译头友好支持,无需手动处理预编译头文件中的依赖关系(target_precompile_headers);也可以使用clang替换gcc;在内存足够、核心足够的机器上,预编译头可能是一种负担(如何筛选预编译的文件,标准库头文件)。
关于预编译机制:
第一次编译并保存这个预编译头状态比编译这些代码慢(时间代价20%-200%);
重新加载已保存的状态很快;
足够内存的系统,预编译头文件机制速度比编译单个标准头文件快很多;
根据头文件使用频率和稳定性分层(结论一致);
优化:(不行)
Plain Text incA.h : // 变动频率最低,几乎不变的标准库 #include <iostream> #include <string> #include <vector> incB.h : // 变动频率次低, 偶尔变化的头文件 #include <incA.h> // 比如包含<iostream> <string> <vector> ,先生成 gchA 预编译头 #include <zCore.h> // 编译器从这里 先加载 gchA, 开始编译 incB.h 生产 gchB 预编译头 #include <zBase.h> main.cpp : #include <incB.h> // 包含incB, 会导入 gchB |
GCC编译优化应用预编译头相关推荐
- 每日一题(36)—— 什么是预编译 , 何时需要预编译?
什么是预编译 , 何时需要预编译? 1.总是使用不经常改动的大型代码体. 2.程序由多个模块组成,所有模块都使用一组标准的包含文件和相同的编译选项.在这种情况下,可以将所有包含文件预编译为一个预编译头 ...
- java预编译啥意思_java预编译 java jdbc 预编译语句和普通语句的区别
java中什么是预编译precompile?总有一天你恍然大悟,父母是你花心思,花时间最少,却最爱你的人. 什么是预编译?起什么作用的? . 在java中如何实现预编译?祝你幸福!这句话真俗.小编会祝 ...
- Android首次启动时间长优化之预编译提取Odex
提示!应用程序的安装有两种情况,第一:首次启动系统时安装.第二:系统启动完毕后安装. 本篇博文基于第一种安装场景.在系统首次启动的场景中,系统会对/system/app./system/priv-ap ...
- 预编译文件,预编译头文件认识
一.概念: 1.预编译:就是编译器首先编译某个文件(称为预编译头文件),然后将这个编译结果保存起来,之后如果有其他源文件include了这个"预编译头文件"的时候,则自动从这个编译 ...
- android预置app 不分解odex,Android首次启动时间长优化之预编译提取Odex
提示!应用程序的安装有两种情况,第一:首次启动系统时安装:第二:系统启动完成后安装.本篇博文基于第一种安装场景.在系统首次启动的场景中,系统会对/system/app./system/priv-app ...
- JPA 中 sql 预编译 -- EntityManager 使用 预编译
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家.点击跳转到教程. 实现方式 : 1. 注入em: @PersistenceContextprivate EntityM ...
- js中立即执行函数会预编译吗_JavaScript预编译过程
什么是预编译? 当js代码执行时有三个步骤: 1.语法分析,这个过程检查出基本的语法错误. 2,预编译,为对象分配空间. 3,解释执行,解释一行执行一行,一旦出错立即停止执行. 预编译发生在代码执行的 ...
- java 正则 预编译_正则表达式预编译功能的正确使用
在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度. 同时,Pattern要定义为static final静态变量,以避免执行多次预编译. 下面,我们列举两类使用正则的场景,来具体说明预 ...
- java sql in 预编译_JAVA使用预编译防止SQL注入
原标题:JAVA使用预编译防止SQL注入 这是一条正常的SQL语句: SELECT * FROM users WHERE name ='$var'; 但是当变量var为 ' or true or '时 ...
最新文章
- 一款java游戏伐木建造_伐木建造模拟器
- 异常-自定义异常 和 throw和throws的区别
- 【算法竞赛学习】数字中国创新大赛智慧海洋建设-Task3特征工程
- Spring Boot-使用JDBC连接并检索数据库(Mysql在Docker中)
- 为什么Redux需要reducer成为“纯函数”
- 网站部署到云服务器需要哪些文件,云服务器能部署哪些应用?怎么部署?
- 嘿,老李,又在写BUG呢?
- Pytorch——批标准化(层归一化)
- 【BIEE】超过了已配置的最大允许输入记录数
- CocoStudio 骨骼动画制作过程
- win7 64位系统HP LaserJet P1008 / HP LaserJet P1008 P1007 驱动安装成功,但无法打印的原因...
- 2022年北京航空航天大学计算机考研复试分数线
- 网络型多媒体计算机教室功能是,多媒体网络教室中的信息技术教学
- 华为鸿蒙家电物联网,华为“鸿蒙”来了:本身是为了做物联网,将比安卓速度快60%...
- 传统数据与大数据对比
- Linux创建一个有空间大小限制的目录提供给ftp用户
- 基于java spring框架开发部标1078视频监控平台精华文章索引
- Python+CV2实现黑色背景的旋转图片矫正回水平图片
- 网页聊天框发送表情图片实现方法
- Win10 蓝屏 SFC /SCANNOW发现损坏文件
热门文章
- B站直播弹幕获取 - 用python写一个B站弹幕姬吧
- 【Android取证篇】华为云备份支持备份的数据类型-不支持应用分身
- Vue3.x全家桶之Vue组件化开发(二)
- stm32f10x_conf.h 与 stm32f10x.h
- 【三年面试五年模拟】算法工程师的独孤九剑秘籍(第九式)
- iOS 如何获取app分享的文档类数据
- lol澳洲服务器如何注册账号,LOL手游澳服怎么注册 云顶之弈手游澳服安装注册方法[多图]...
- 安卓手机数据信号好的服务器,2018年度手机信号榜公布 这10款手机信号最好!...
- 深度学习寒冬将至?看CNCC2019聚焦AI热点,洞见未来!
- ecstore导出报表 importexport文件需要配置的