cpp 规范 #
尽量不要使用全局变量 #
编译器很难对全局做优化。可以将全局变量打包在一起,做成 Context 对象,全文传递
如果一定要用全局变量,使用 static 修饰 #
使用 static 后,编译器确定变量不会被其他编译单元访问,可以做很多静态分析
使用 c++17 std::string_view 替换 std::string #
cpp17 引入 string_view 来优化 std::string 的性能。它本身不 own 内存,只维护了一个指针和长度。其本质是一个 const char* 指针,字符串常量的初始化建议使用 std::string_view
不要在头文件中定义容器对象 #
在容器中定义的对象会被创建出来,不管 cpp 里有没有用到。而且任何 include 该头文件所编译出来的 .o 文件中也会创建这些对象。即 链接出来的二进制中有很多份对象
有时希望在头文件中定义让代码更一致,如:
static const std::unordered_map<std::string, int> map = {{"asdf", 4}}
这是一种低效的实现,所有 include 这个头文件的都会初始化该对象,在链接时,这些对象都会被链接进产物并做运行时初始化
可以通过维护一个 def 文件来描述映射关系
#ifndef MAP
#defind MAP(x, y)
#endif
MAP(asdf, 4)
...
#undef MAP
在需要该对象的地方,加上:
const std::unordered_map<std::string, int> map = {
#define MAP(x, y) {#x, y},
#include "xxx.def"
}
多用 std::array 而不是 std::vector #
array 是分配在 栈 上,vector 分配在 堆 上,如果分配在堆上,分配和释放速度慢,且堆操作为动态,编译器优化是静态,无法知道内存情况,很难做优化
尽量避免指针的转换 #
int foo(short* s, int* i) {
*i = 1;
*s = 2;
return *i;
}
int main() {
int x = 0;
x = foo((short*)&x, &x);
std::cout >> x;
}
// g++ a.cpp 输出 2
// g++ a.cpp -O2 输出 1
对 cast 完的指针做 deference 是 undefined behavior,编译器可能会对其做优化。编译器提供选项 -fno-strict-aliasing
来禁掉该优化,但是关掉 strict-aliasing 对程序性能有巨大影响
-
strict-aliasing
编译器在做别名分析(aliasing analysis)时,需要知道两个指针所指向内存是否存在交叉。该信息决定了指令之间的依赖关系,以及一块内存是否重新 load
如:
int a = *p;
慎用异常 #
异常被设计出来应该是做极端情况的容错处理的,不应该用来处理代码逻辑
- 对于不抛出异常的逻辑,为零开销
- 如果抛出异常,运行库会做两次调用栈回溯,过程非常耗费时间
x86 架构的一场实现本身不会对正常路径的程序性能有太多影响,但是会要求编译器在 eh_frame 中生成一些数据,会影响代码的 size,同时由于会插入很多代码处理异常发生情况,所以会影响 icache。如果确定代码中不会有异常,可以通过 -fno-exceptions
关掉
性能优化方法 #
cache 优化 #
写对性能友好的代码。相较于 CPU 的运算速度,内存访问耗时相当严重
如 遍历二维数组注意循环中元素的读取顺序
CPU 优化 #
-
避免拷贝
使用 emplace 簇函数
定义移动构造函数和移动复制函数
确认安全的前提下,使用 std::string_view 替代 std::string
函数签名 #
函数名 + 参数个数 + 参数类型 + 参数顺序 + const cv
const_iterator 和 iterator #
- 尽量使用 const_iterator 而不是 iterator