1. 前言
随着程序规模的增大,C++编译速度成为了一个非常重要的问题。特别是对于复杂的项目,每次代码修改都需要重新编译整个工程,这会耗费大量时间。因此,优化C++编译速度是一项非常重要的工作。
2. 编译过程
C++编译过程分为以下几个步骤:
2.1 预处理
预处理器会处理源代码文件,将所有以#
开头的语句进行处理。例如,#include
指令会将指定的文件内容插入到当前文件中。预处理器还会进行条件编译,例如#ifdef
和#ifndef
指令等。处理完成后会输出一个.i文件。
#ifndef TEST_H
#define TEST_H
#include "header1.h"
#include "header2.h"
#endif // TEST_H
预处理器的速度通常非常快,但是如果存在大量的#include
指令,那么预处理的速度可能会变慢。
2.2 编译
编译器会将预处理器输出的.i文件翻译成汇编代码,然后将汇编代码翻译成机器代码。编译器通常会进行各种优化,例如常量折叠、内联等。
2.3 链接
链接器会将多个目标文件合并成一个可执行文件,链接过程中会进行符号解析和重定位等操作。
编译过程中,最耗时的步骤通常是编译和链接过程,因此我们需要找到一些方法来优化这些过程。
3. 优化编译速度的方法
3.1 使用前置声明
在C++中,如果一个类被定义在头文件中,那么每次include这个头文件的时候,编译器都会重新编译这个头文件。这会浪费大量的时间。因此,我们可以使用前置声明来避免这种问题。
// test.h
#ifndef TEST_H
#define TEST_H
#include "header1.h"
#include "header2.h"
class A;
#endif // TEST_H
// test.cpp
#include "test.h"
#include "A.h"
void func(A& a) {
// do something
}
这样,我们就可以避免每次#include "A.h"
的时候重新编译test.h
。
3.2 减少头文件的依赖
如果某个头文件被大量地include,那么每次修改这个头文件都会导致重新编译所有用到这个头文件的源文件。因此,我们需要尽量减少头文件的依赖。
例如:
// test.h
#ifndef TEST_H
#define TEST_H
#include "header1.h"
#include "header2.h"
#include "header3.h"
#include "header4.h"
#include "header5.h"
#endif // TEST_H
// test.cpp
#include "test.h"
void func() {
header3_func();
header4_func();
}
在上面的例子中,如果我们修改了header4.h
,那么test.cpp
和所有include了test.h
的源文件都需要重新编译。
我们可以将test.h
中不需要的头文件去掉,例如:
// test.h
#ifndef TEST_H
#define TEST_H
#include "header1.h"
#include "header2.h"
#endif // TEST_H
// test.cpp
#include "test.h"
#include "header3.h"
#include "header4.h"
void func() {
header3_func();
header4_func();
}
这样,test.cpp
和其他源文件只需要重新编译test.cpp
和header4.cpp
即可。
3.3 分离编译
分离编译指的是将一个大的源文件分成多个小的源文件编译。这样,每次只需要重新编译修改的源文件,而不需要重新编译整个工程。
例如:
// big_file.cpp
#include "header1.h"
#include "header2.h"
#include "header3.h"
void func1() {
// ...
}
void func2() {
// ...
}
void func3() {
// ...
}
// small_file1.cpp
#include "header1.h"
void func1() {
// ...
}
// small_file2.cpp
#include "header2.h"
void func2() {
// ...
}
// small_file3.cpp
#include "header3.h"
void func3() {
// ...
}
在上面的例子中,big_file.cpp
被拆分成了三个小的源文件,每个文件只包含和自己相关的头文件。如果我们修改了small_file1.cpp
,那么只需要重新编译这个文件即可。
3.4 使用预编译头
预编译头是编译器的一个优化选项,它可以缩短预处理的时间。预编译头指的是一些通用的头文件,例如STL、Windows API等。预编译头只需要在第一次编译的时候生成一次,后续编译可以重复使用,因此可以大大缩短编译时间。
在Visual Studio中,预编译头的文件名为stdafx.h
,需要将其包含在每个源文件的最开头:
// stdafx.h
#include <iostream>
#include <string>
#include <vector>
// test.cpp
#include "stdafx.h"
int main() {
std::vector<int> vec = {1, 2, 3};
std::string str = "hello";
std::cout << str << std::endl;
}
在使用预编译头之前,需要将编译选项设置为使用预编译头:
cl.exe /Yu"stdafx.h" /Fp"stdafx.pch" /c test.cpp
cl.exe是Visual Studio自带的命令行编译器,/Yu指定预编译头文件,/Fp指定预编译头文件的生成文件名,/c指定生成.obj文件。
3.5 使用多核编译
C++编译器通常是单线程的,因此可以使用多线程来加快编译速度。通常可以使用-j
选项来指定编译器使用多少个核进行编译。例如,使用4个核进行编译:
make -j4
在Visual Studio中,可以在编译选项中开启并行编译:
Project Properties -> Configuration Properties -> C/C++ -> General -> Multi-processor Compilation
4. 总结
优化C++编译速度是一项非常重要的工作,可以极大地提高开发效率。我们可以使用前置声明、减少头文件的依赖、分离编译、使用预编译头、使用多核编译等方法来优化编译速度。同时,也需要注意一些不良的编程习惯,例如在头文件中定义函数、使用过多的宏定义等,这些会影响编译速度。