首页 > 解决方案 > 考虑缓存一致性的高性能应用程序的 POD 数学结构类的 C++ 值传递与引用传递选择

问题描述

对于许多高性能应用程序,例如游戏引擎或金融软件,缓存一致性、内存布局和缓存未命中的考虑对于保持流畅的性能至关重要。随着 C++ 标准的发展,特别是随着Move SemanticsC++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 数学类以用于高性能应用程序时,使用的一般最佳实践指南是什么?

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

鉴于上述情况,对于现代 C++ 编译器(C++14 及更高版本)何时使用按值传递与按引用传递来最小化缓存未命中并提高缓存一致性,有什么好的指导方针?什么时候有人会说这个 POD 数学结构太大而无法按值传递,例如 4v4 仿射变换矩阵,假设使用 float32,其大小为 64 字节。在做出此决定时,在堆栈上声明的 Vector,或者更确切地说是任何小的 POD 数学结构与作为成员变量引用是否重要?

我希望有人可以提供一些分析和见解,以便为上述情况建立一个良好的现代最佳实践指南。我相信随着 C++ 标准的发展,何时将 PBV 与 PBR 用于 POD 类的界限变得更加模糊,尤其是在最大限度地减少缓存未命中方面。

标签: c++performancememory-managementmove-semanticsdata-oriented-design

解决方案


我看到问题标题是关于按值传递与按引用传递的选择,尽管听起来您更广泛地追求的是有效传递 3D 矢量和其他常见 POD 的最佳实践。传递数据是基本的,并且与编程范式交织在一起,因此对于最佳方式没有达成共识。除了性能之外,还有一些需要权衡的因素,例如代码可读性、灵活性和可移植性,以决定在给定应用程序中采用哪种方法。

也就是说,近年来,“面向数据的设计”已成为面向对象编程的流行替代方案,尤其是在视频游戏开发中。基本思想是考虑程序需要处理的数据,以及如何在内存中组织所有数据以获得良好的缓存局部性和计算性能。在 CppCon 2014 上有一个很棒的讨论:Mike Acton 的“面向数据的设计和 C++”

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


推荐阅读