C++ 如何为性能设计对象

C++ 如何为性能设计对象,c++,performance,cpu-architecture,C++,Performance,Cpu Architecture,在最近阅读一篇关于物理引擎开发的文章时,我遇到了一个我以前从未考虑过的设计决策。这与CPU寻址内存中原始字节的方式有关 考虑以下类别: class Foo { public: float x; float y; float z; /* Constructors and Methods */ private: float padding; } 作者声称,在x86体系结构中,填充将对象的大小增加为四

在最近阅读一篇关于物理引擎开发的文章时,我遇到了一个我以前从未考虑过的设计决策。这与CPU寻址内存中原始字节的方式有关

考虑以下类别:

class Foo
{
    public:
        float x;
        float y;
        float z;

        /* Constructors and Methods */

    private:
        float padding;
}
作者声称,在x86体系结构中,填充将对象的大小增加为四字,从而显著提高了性能。这是因为4个单词在记忆中比3个单词更清晰,这是什么意思?用冗余数据填充对象以提高性能对我来说似乎很矛盾

这也引出了另一个问题,那么大小为1或2个单词的对象呢? 如果我的班级是这样的:

class Bar
{
    public:
        float x;
        float y;

        /* Constructors and Methods */

    private:
        /* padding ?? */
}
我是否应该给这个类添加填充,使它在内存中更干净?

处理器不像人类那样逐字节“读取”内存,而是逐块处理,大小随处理器而异。这称为内存访问粒度

通过“内存对齐”对象,访问时间可能会更快,并且还可以避免数据碎片

您可以阅读有关数据对齐的更多信息

编辑:我不是说这是一个好的或坏的实践,只是分享我对它的了解。

编译器有责任决定什么是合理的填充(假设典型的访问模式)。编译器确实比你所知道的要多得多。此外,您的机器将与您一起使用几年;该程序将持续数十年,在各种平台上运行,其使用模式令人难以置信。对于今天的i7来说最好的可能是明天的i8或ARMv11最差的


为了追求难以捉摸的“性能”而混淆代码完全是错误的。永远记住,你的时间(编写、测试、调试、一周后再次理解、修改代码)比可能浪费的计算机时间要昂贵得多(除非所说的代码每天在数百万台机器上运行数千次)。代码调整毫无意义,除非你有确凿的事实表明性能还不够,并且测量结果告诉你改变结构是一个值得担心的瓶颈。

回答这个问题有两件非常重要的事情要说

首先,如果您打算调整代码以获得性能优势,并且如果您认为这是值得的(无论出于何种原因),那么您必须首先编写一个基准测试。您必须能够尝试这两种方法并测量差异

其次,这种调整将取决于汇编语言如何与硬件交互。您必须能够阅读汇编语言代码并理解不同的指令集和硬件访问模式,才能理解这些调整可能起作用的原因


最后,你的问题没有单独的答案。这取决于这些对象是单独分配还是在集合中;旁边是否有其他物体;以及编译器如何为每种情况生成代码。在所有可能的情况下,在二次方边界上对齐要比未对齐快,但适合缓存的集合要比不适合缓存的集合快。我不希望填充8或4个字节来提高性能,但如果这很重要,我会尝试并测试结果。

如果您对此感兴趣,还应该研究面向数据的设计。@简单感谢,我将在这种特殊情况下检查它,它有助于编译器更轻松地生成SIMD代码,而无需洗牌。至于缓存,这是一把双刃剑,不存在唯一正确的答案。如果对象大小不是二的幂,则最终会跨越多条缓存线,理论上可能会更慢。实际上,这不是因为您只处理一个对象,而是因为您连续处理多个对象,所以预取器已经获取了相邻的缓存线。另一方面,较小的对象占用较少的缓存线。如果您不介意编译器特定的代码(gcc和clang支持此功能),也可以使用attribute.No。缓存线通常为64或128字节(通常为64字节)。但是4*16=64,所以四个对象可以很好地匹配缓存线。除此之外,您可以使用fast SSE指令获取16个对齐字节。获取未对齐的16字节要慢得多。无法获取12个字节。因此,如果编译器使用SSE math,它必须在没有填充的情况下进行洗牌。另一方面,如果编译器生成“普通”标量代码,那么结构中的3而不是4个元素可能会更快,因为所涉及的内存会减少25%,因此缓存未命中的可能性会减少25%,编译器在做出优化决策时可能装备不足,因为编程语言没有提供一种方法来传达程序员已知的一些信息。有些信息可以由编译器发现,但需要异常的努力(例如,整个程序分析)。有些信息依赖于输入(甚至概要文件引导的优化也可能不会进行程序员显而易见的优化,例如,应该使用哪种排序算法)。填充不需要混淆,但这种优化很可能是脆弱的w.r.t.硬件、程序、输入、编译器更改。