以下是几条优化C++的方法
如何获取最高性能?
- 选择正确的算法
- 使用低overhead的语言(C++、Rust)
- 正确的编译选项
- 了解底层硬件
了解底层硬件可以帮助我们获取更多的性能
仅仅通过C++代码是无法预测性能的,性能很依赖CPU与内存的底层实现。
![](http://www.resetoter.cn/wp-content/uploads/2020/10/image-12.png)
上图为Intel大概的构架。
分支预测
![](http://www.resetoter.cn/wp-content/uploads/2020/10/image-13.png)
数组排序与不排序,性能会差一倍
![](http://www.resetoter.cn/wp-content/uploads/2020/10/image-14.png)
![](http://www.resetoter.cn/wp-content/uploads/2020/10/image-15.png)
通过Vtune可以看到相关耗时
![](http://www.resetoter.cn/wp-content/uploads/2020/10/image-16.png)
其耗时来自预测。
![](http://www.resetoter.cn/wp-content/uploads/2020/10/image-17.png)
构架中的BPU用于分支预测,分支预测失败越多耗时就越长。
![](http://www.resetoter.cn/wp-content/uploads/2020/10/image-18.png)
在第五个时间戳的时候cpu并不知道要执行啥,所以会进行分支预测,继续执行。
如果预测失败可能会导致15~20个cycle
![](http://www.resetoter.cn/wp-content/uploads/2020/10/image-19.png)
![](http://www.resetoter.cn/wp-content/uploads/2020/10/image-20.png)
编译器也会做相应优化,如果数字为int时编译器会自动去除分支。
![](http://www.resetoter.cn/wp-content/uploads/2020/10/image-21.png)
![](http://www.resetoter.cn/wp-content/uploads/2020/10/image-22.png)
![](http://www.resetoter.cn/wp-content/uploads/2020/10/image-23.png)
除了分支以外,以下这几项也会导致分支预测:
- 函数指针
- 函数返回地址
- 虚函数
![](http://www.resetoter.cn/wp-content/uploads/2020/10/image-24.png)
以下代码,如果同一种同时执行效率更高。
![](http://www.resetoter.cn/wp-content/uploads/2020/10/image-25.png)
命中缓存
![](http://www.resetoter.cn/wp-content/uploads/2020/10/image-26.png)
int的vector,vector的offset不同。
![](http://www.resetoter.cn/wp-content/uploads/2020/10/image-27.png)
发现Offset不同的情况下性能也不一样。
![](http://www.resetoter.cn/wp-content/uploads/2020/10/image-28.png)
这个性能的差异是L1 Data Cache引起的。
L1的实现是通过硬件实现的hashtable实现的 Key占8字节,cacheline占64字节
![](http://www.resetoter.cn/wp-content/uploads/2020/10/image-29.png)
![](http://www.resetoter.cn/wp-content/uploads/2020/10/image-30.png)
我们可以通过l1d.replacement看到cache missing的次数
![](http://www.resetoter.cn/wp-content/uploads/2020/10/image-31.png)
数值计算
![](http://www.resetoter.cn/wp-content/uploads/2020/10/image-32.png)
上述代码,不同的数字计算的速度并不一样。
![](http://www.resetoter.cn/wp-content/uploads/2020/10/image-33.png)
为整数时计算比0.3等特殊数字要快得多,值得注意的是0.5也很快(二次幂)。
![](http://www.resetoter.cn/wp-content/uploads/2020/10/image-34.png)
这个实际上和硬件相关的处理模块也有关
![](http://www.resetoter.cn/wp-content/uploads/2020/10/image-35.png)
![](http://www.resetoter.cn/wp-content/uploads/2020/10/image-36.png)
通过fp_assist.any可以看到浮点计算的开关次数
![](http://www.resetoter.cn/wp-content/uploads/2020/10/image-37.png)
在Intel的芯片上可以用相应的开关加速计算
![](http://www.resetoter.cn/wp-content/uploads/2020/10/image-38.png)
多线程访问
![](http://www.resetoter.cn/wp-content/uploads/2020/10/image-39.png)
同时访问同一块内存,线程越多,速度越慢
![](http://www.resetoter.cn/wp-content/uploads/2020/10/image-40.png)
![](http://www.resetoter.cn/wp-content/uploads/2020/10/image-41.png)
![](http://www.resetoter.cn/wp-content/uploads/2020/10/image-42.png)
第一个核读入了A,
![](http://www.resetoter.cn/wp-content/uploads/2020/10/image-43.png)
第二个核读取的时候,会从第一个核中读入,会比从内存中读取更多
![](http://www.resetoter.cn/wp-content/uploads/2020/10/image-44.png)
第二个核写入的时候第二个核的数据和第一个核不同步了。
导致需要将数据同步到第一个核
![](http://www.resetoter.cn/wp-content/uploads/2020/10/image-45.png)
为了解决这个问题,我们可以将不同的操作数放到不同的cacheline中。
例如原来的cache分布是这样子:
![](http://www.resetoter.cn/wp-content/uploads/2020/10/image-46.png)
我们可以将数字放到不同的cacheline中,
![](http://www.resetoter.cn/wp-content/uploads/2020/10/image-47.png)
这样就不会产生写入的冲突。
![](http://www.resetoter.cn/wp-content/uploads/2020/10/image-48.png)
用这个命令可以看到不同核数据失效的次数
源码
![](http://www.resetoter.cn/wp-content/uploads/2020/10/image-49.png)