分析C++工程编译时间过长的原因

举报
Jet Ding 发表于 2020/09/30 16:25:58 2020/09/30
【摘要】 最近接到一个任务,是分析一下为何一些C++工程编译时间过长,是否有好的方案来改善一下编译的速度。本文我们来探讨一下可能影响到C++编译时间的一些因素。

【引言】


大家好,最近接到一个任务,是分析一下为何一些C++工程编译时间过长,是否有好的方案来改善一下编译的速度。本文我们来探讨一下可能影响到C++编译时间的一些因素。


【头文件相关问题】


【没有把标准库头文件放在预编译头文件中】


预编译头文件中应该放入很少改动的那些头文件,比如系统级的头文件,标准库的头文件,从而节省反复编译造成的时间开销。


不要把已有头文件放入预编译头文件中,由于私有头文件会经常改动,每次改动都会导致整个预编译头文件编译一次,会消耗很多时间。


【没有把标准库头文件放在预编译头文件中的问题修复】

把不常改动的标准库头文件和系统级头文件放入预编译头文件中。


【头文件在一个编译单元中的多次引用编译】


如果没有预编译头判断检查守卫的话,一个头文件被引入几次就会被编译几次,会消耗不必要的时间。


【头文件在一个编译单元中的编译问题的修复】


在定义头文件时使用如下格式:

#pragma once

#ifndef filename_h

#define filename_h

 

// Header declarations / definitions

 

#endif

 

 

如上的示例是预编译头的条件判断,用这样的判断可以避免一个头文件在一个编译单元中被编译多次。


【引入了不需要的头文件】


有时候在写程序的时候,由于种种原因工程师引入了一些不需要的头文件。这些引入的头文件会消耗编译的时间。


【引入了不需要的头文件问题的修复】


删除此种情况下引入的不必要头文件。


【头文件的重复多次编译问题】

一个工程中一般存在很多个编译单元,一个编译单元对应一个编译输出如obj, so文件,每个编译单元会包含成百上千个头文件,这些头文件在编译对应编译单元时会被加载并编译。

 

一个很普遍的情况是一个工程中的头文件会被多个编译单元引用,这样在编译的时候,这些头文件会被编译多次。这些时间开销累加起来是非常可观的。


【头文件的重复多次编译问题修复】

要修复头文件重复多次编译的问题,我们需要对编译单元进行整合,尽可能地减少多个编译单元对于某个头文件的依赖情况。


【头文件中使用using namespace的编译问题】

这种情况可能会引起名字冲突的编译错误。所以要避免这种情况。


【头文件中使用using namespace的编译问题的修复】


可以这样修复:

class MyClass

{   

private:

    NameSpace1::Class1 _member;

}

 

或者

class MyClass

{  

    namespace n=NameSpace1

private:

    n::Class1 _member;

}

 

 

【间接引用头文件的编译问题】


下面的例子是一个间接引用头文件的例子。在ClassB的头文件中引入ClassA的头文件以后,编译器会打开ClassA的头文件进行编译。这个消耗实际上我们是可以避免的。

 

#include "classa.h"

 

class ClassB{

    ClassA *m_pClassA;

};

在上面的代码中,我们引入了classa的头文件;

 

【间接引用头文件问题修复】


class ClassA;

class ClassB{

    ClassA *m_pClassA;

};


在上面的代码中,我们通过前向声明ClassA的方式避免引入classa的头文件。

 

通过以上的改动可以提高编译的速度。

 

具体到数量级要根据具体项目来定,这里有个粗略的估算,如果你的头文件比较庞大,而又在一个项目中被多个工程所引用,通过这个方法有可能帮助你减少几个小时的编译时间。


【一个头文件种包含了多个不相关的功能组定义】


如果在一个头文件中包含了多个不相关的功能组,这个头文件必然会被不同的模块功能组引用,这可能会导致这个头文件被多次编译。从而导致了编译时间的增长。


【一个头文件种包含了多个不相关的功能组定义问题修复】


修复这个问题的方法是把多个功能组从这一个头文件中分离出来,创建对应的功能单一的头文件。然后在对应的功能组中只引用对应的那个头文件。


【其他不好的头文件使用习惯】


【头文件中引入cpp文件】


在公共头文件中引入源文件不是一个好习惯。有时候程序员希望在多个源文件中使用一些共享代码,于是通过引入一个源文件的方式来做。


【头文件中引入cpp文件修复】

如果属于类似情况的话,可以把这些共享代码放在内部的头文件中。


【在公共头文件中放置了过多的信息导致信息泄露】


在开发共享库的时候,我们会把暴露出的头文件如何调用的信息说清楚就行了,有时候画蛇添足的说明这个功能是如何开发实现的。


【在公共头文件中放置了过多的信息导致信息泄露修复】

删除与公共头文件调用无关的信息。


【头文件不能自编译】


把头文件引入一个空的源文件进行编译测试,如果存在编译错误,说明该头文件无法自编译。

头文件无法自编译会在工程代码庞大起来以后发生一些随机的误导性的编译错误,使得问题排查工作变得艰难。


【头文件不能自编译问题修复】


需要通过自下而上的方法修复头文件不能自编译的问题。


【模板相关的问题】


【模板的使用会增加编译时间】


【单层模板】


C++中使用模板时,编译器会把每处使用模板的地方展开再编译,这个展开的过程自然会增加时间的开销。


【嵌套模板】


template<typename Ttypename... ArgTypes>

int add(T tArgTypes... args)

{

    return t + add(args...);

}

 

更为可怕的情况是当在代码中使用嵌套模板的时候,也就是模板中再使用另一层模板或者更多层模板,编译器需要首先解释并展开这些模板以及嵌套的模板,这个时候编译器解释和展开的时间消耗会是惊人的。

【多个模板参数


template <typename Ttypename U>

struct foo { };

在上例中,如果T8种可能,U8种可能,编译器会展开成64种实现。这会极大的增加编译时间的开销。


【模板会导致超复杂的变量类型和超长的变量函数名】


由于编译器在解释展开模板时是通过程序来做的,编译程序可能会生成极其复杂的变量类型以及超级长度的变量名和函数名。这些程序生成的代码会额外的增加编译时间和链接时间。


【模板编译问题的修复】


尽量不使用嵌套模板。

尽量使用一个模板参数。


【过长的命名】


com.sun.java.swing.plaf.nimbus.InternalFrameInternalFrameTitlePaneInternalFrameTitlePaneMaximizeButtonWindowNotFocusedState

 

上例是Java类的一个命名例子。

有时候程序员会选择使用过长的命名,比如类名,函数名,变量名,文件名等等,这些也会导致编译时间和链接时间的增长。


【过长命名问题的修复】


尽量使用短而又可理解的命名。

 

【过多的编译单元】


过多的编译单元无疑会增加编译和链接的时间。


【过多的编译单元问题修复】


可以通过脚本在编译前把源文件合并。比如sqlite的编译过程这样可以节省编译和链接的时间。

https://sqlite.org/src/doc/trunk/README.md

For example:

    tar xzf sqlite.tar.gz    ;#  Unpack the source tree into "sqlite"

    mkdir bld                ;#  Build will occur in a sibling directory

    cd bld                   ;#  Change to the build directory

    ../sqlite/configure      ;#  Run the configure script

    make                     ;#  Run the makefile.

    make sqlite3.c           ;#  Build the "amalgamation" source file

    make test                ;#  Run some tests (requires Tcl)

以上展示的是sqlite的编译过程。其中高亮部分就是编译一个合并后的源文件。

 

【使用并行编译机制】


很多编译器和开发工具支持多内核多CPU条件下的并行编译。比如gnu make下使用-j[N]选项: make –j 4, 同时启动4个任务。

Visual Studio下使用并行模式编译。

在可以充分使用多核多CPU的情况下,并行编译可以提高编译速度。


【编译优化机制最小化】


总的规律是使用的优化选项越多,编译器做的事情就越多,花的时间就越长。

 

【共有代码的共享最大化】


把不经常更改的代码放在库里面,通过预编译设置,可以提高编译速度,同时把大量的共有代码放在一个so或者dll 里面又可以提高链接速度。

 

【提高电脑配置】


通过使用更高的CPU,更多的内存,以及固化硬盘等措施提高编译电脑的性能来提高编译速度。

 

【提高网络速度】


如果在编译过程中需要访问网络,那么通过提高网速的方法也可以提高编译的速度。

 

【小结】


以上内容对于C++工程编译过程中可能存在的问题,尤其是影响编译速度的问题进行了探索。 如果您所在的团队正在进行相关的优化,不妨做个参考,希望本文能有所帮助。欢迎大家评论,留言,批评和指正。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。