C++ C++;考虑缓存一致性的高性能应用程序的POD数学结构类的通过值与通过引用的选择

C++ C++;考虑缓存一致性的高性能应用程序的POD数学结构类的通过值与通过引用的选择,c++,performance,memory-management,move-semantics,data-oriented-design,C++,Performance,Memory Management,Move Semantics,Data Oriented Design,对于许多高性能应用程序,如游戏引擎或金融软件,缓存一致性、内存布局和缓存未命中对于保持平滑性能至关重要。随着C++标准的发展,特别是在C++中引入了移动语义< /强>和 C++ 14 ,对于基于数学的类POD类,在绘制逐值传递线时,变得越来越不清楚。 考虑常见的Vector3类: class Vector3 { public: float32 x; float32 y; float32 z; // Implementation Functions below (all n

对于许多高性能应用程序,如游戏引擎或金融软件,缓存一致性、内存布局和缓存未命中对于保持平滑性能至关重要。随着C++标准的发展,特别是在C++中引入了<强>移动语义< /强>和<强> C++ 14 ,对于基于数学的类POD类,在绘制逐值传递线时,变得越来越不清楚。 考虑常见的Vector3类:

class Vector3
{
public:
   float32 x;
   float32 y;
   float32 z;
   // Implementation Functions below (all non-virtual)...
}
这是游戏开发中最常用的数学结构。它是一个非虚拟的12字节大小的类,甚至是64位的,因为我们显式使用IEEE float32,每个float使用4个字节。我的问题如下-当决定通过高性能应用程序的价值或参考来通过POD数学课程时,一般最佳实践指南是什么?

回答此问题时需要考虑的一些事项:

  • 可以安全地假设默认构造函数不初始化任何值
  • 可以安全地假设,任何POD数学结构均未使用1D以外的阵列
  • 显然,大多数人通过值传递4-8字节的POD常量,所以这里似乎没有太多争论
  • 当这个向量是类成员变量而不是堆栈上的局部变量时会发生什么?如果使用了passby引用,那么它将使用类上变量的内存地址,而不是堆栈上本地对象的内存地址。这个用例重要吗?使用PBR时的这种差异会导致更多的缓存未命中吗
  • 使用或不使用SIMD的情况如何
  • 移动语义编译器优化怎么样?我注意到,当切换到C++14时,当通过值传递相同向量时,编译器通常会使用移动语义,特别是当它是常量时。我通过仔细阅读装配分解图观察到了这一点
  • 当对这些数学结构使用“按值传递”和“按引用传递”时,const是否会对编译器优化产生很大影响?见上一点
<> P>以上,什么是使用C++的编译器(C++ 14及以上)时,如何使用过关值和参考值来最小化缓存丢失,提高缓存一致性?什么时候可能有人会说这个POD数学结构对于传递值来说太大了,比如4v4仿射变换矩阵,假设使用float32,它的大小是64字节。在做出这个决定时,在堆栈上声明的向量,或者更确切地说是任何小的POD数学结构,与作为成员变量引用的向量相比,是否重要


我希望有人能提供一些分析和见解,以便为上述情况建立良好的现代最佳实践指南。我相信随着C++标准的发展,特别是关于最小化缓存缺失的时候,对于使用POV类的PBV和PBR来说,这一行变得更加模糊。虽然这听起来像是你更广泛地追求的,但这是高效地传递3D向量和其他常见吊舱的最佳实践。传递数据是最基本的,并且与编程范式交织在一起,因此在最佳方式上没有共识。除了性能,还需要考虑代码可读性、灵活性和可移植性等因素,以决定在给定应用程序中采用哪种方法

也就是说,近年来,它已经成为面向对象编程的流行替代方案,特别是在视频游戏开发中。基本思想是考虑程序需要处理的数据,以及如何在内存中组织所有这些数据以获得良好的缓存局部性和计算性能。2014年CppCon上对此进行了精彩的讨论:


例如,以Vector3为例,通常情况下,一个程序不仅有一个3D向量,还有许多3D向量,它们都以相同的方式处理,比如说,都经历相同的几何变换。面向数据的设计表明,在内存中连续排列向量是一个好主意,它们在批处理操作中一起转换。这改进了缓存并创造了利用SIMD指令的机会。您可以使用实现这个示例。向量可以使用3xN形状的
Eigen::Matrix
来表示,以存储N个向量,然后使用Eigen的SIMD加速操作进行操作。

其他人将提供更详细的信息,但通过引用传递的底线不需要复制或指定作为参数传递的局部向量,您只需传递对原始文件的引用。如果除了速度之外什么都不重要,我可能会传递一个指针(地址),因为您只有三个
float32
成员和一些正常函数,这样就可以使用
->
操作符进行简单访问。…@DavidC.Rankin是的,这是一个经典的折衷。任何时候->与ptr或一起使用。使用引用时,将取消对该内存位置的引用以检索该值。实现这一点的速度取决于内存与CPU寄存器的距离。使用PBV时,该内存位于堆栈上,更可能靠近CPU。这里的复杂性是现在编译器用现代C++优化移动语义。我不认为按指针传递和按引用传递在取消引用方面有什么区别(没有对ptr进行nullptr检查)。是的,这就是总结,这也是为什么我选择遵从这里的汇编和硬件人员,他们将更加熟悉当前编译器领域如何对其进行优化——不幸的是,我没有跟上他们的步伐