对于许多高性能应用程序,例如游戏引擎或金融软件,缓存一致性、内存布局和缓存未命中的考虑对于保持流畅的性能至关重要。随着 C++ 标准的发展,特别是随着Move Semantics和C++14的引入,对于基于 POD 的数学类,何时绘制按值传递与按引用传递的界限变得不太清楚。
考虑常见的POD Vector3 类:
class Vector3
{
public:
float32 x;
float32 y;
float32 z;
// Implementation Functions below (all non-virtual)...
}
这是游戏开发中最常用的数学结构。它是一个非虚拟的 12 字节大小的类,即使是 64 位,因为我们明确使用 IEEE float32,它每个浮点使用 4 个字节。我的问题如下 -在决定通过价值或参考通过 POD 数学类以用于高性能应用程序时,使用的一般最佳实践指南是什么?
回答这个问题时需要考虑的一些事项:
- 假设默认构造函数没有初始化任何值是安全的
- 可以安全地假设任何 POD 数学结构都没有使用超过一维的数组
- 显然大多数人按值传递 4-8 字节的 POD 常量,所以那里似乎没有太多争论
- 当这个 Vector 是类成员变量而不是堆栈上的局部变量时会发生什么?如果使用引用传递,那么它将使用类上变量的内存地址与堆栈上本地的内存地址。这个用例重要吗?使用 PBR 的这种差异会导致更多的缓存未命中吗?
- 使用或不使用 SIMD 的情况如何?
- 移动语义编译器优化呢?我注意到,当切换到 C++14 时,当链函数调用按值传递相同的向量时,编译器通常会使用移动语义,尤其是当它是 const 时。我通过阅读装配故障观察到这一点
- 当对这些数学结构使用按值传递和按引用传递时,const对编译器优化有很大影响吗?见上一点
鉴于上述情况,对于现代 C++ 编译器(C++14 及更高版本)何时使用按值传递与按引用传递来最小化缓存未命中并提高缓存一致性,有什么好的指导方针?什么时候有人会说这个 POD 数学结构太大而无法按值传递,例如 4v4 仿射变换矩阵,假设使用 float32,其大小为 64 字节。在做出此决定时,在堆栈上声明的 Vector,或者更确切地说是任何小的 POD 数学结构与作为成员变量引用是否重要?
我希望有人可以提供一些分析和见解,以便为上述情况建立一个良好的现代最佳实践指南。我相信随着 C++ 标准的发展,何时将 PBV 与 PBR 用于 POD 类的界限变得更加模糊,尤其是在最大限度地减少缓存未命中方面。