【问题标题】:What is performance penalty for using aggregates in C++?在 C++ 中使用聚合的性能损失是什么?
【发布时间】:2021-10-01 10:04:06
【问题描述】:

考虑这个例子,结构 S 被构造并作为参数传递给函数:

struct S
{
    S() {}
    float vals[64];
};

inline S makeS() { return {}; }

void foo(const S &);

void bar() { foo( makeS() ); }

查看编译器生成的程序集 (https://godbolt.org/z/odoPr9836),它非常小(正如预期的那样)。

但是,如果我们从 S 中删除构造函数,从而将其转换为聚合 (https://godbolt.org/z/qEWKbx5Ps),则程序集的体积会大很多倍。

甚至似乎 MSVC 不执行强制 RVO 并复制聚合:

...
movups  XMMWORD PTR [rcx-128], xmm0
movups  xmm0, XMMWORD PTR [rax-96]
movups  XMMWORD PTR [rcx-112], xmm1
movups  xmm1, XMMWORD PTR [rax-80]
...

由此可知,与普通 C++ 类或结构相比,在实践中使用聚合产生的优化代码要少得多?

【问题讨论】:

  • Relative stackoverflow.com/questions/47853659/… 聚合初始化基本上执行元素方式的复制初始化。
  • “它遵循...” - 不,它没有遵循。您看到差异,因为您的比较不公平。根据定义,聚合初始化必须初始化每个成员。而那些你不提供的将从{}递归初始化。这意味着您 要求 将数组清零。为了进行公平比较,也可以尝试inline S makeS() { S s; return s; },因此默认初始化是在您的聚合(及其子对象上执行的,就像您的 c'tor 一样)。 C++ 对专家非常友好。一个令人不快的事实,但你有它。
  • 同什么?当强制执行默认初始化时,我会看到带有或不带有构造函数的相同程序集。您似乎仍然没有将苹果与苹果进行比较。
  • @StoryTeller 我不认为这样做是一样的。这是未定义的行为,因为您复制了尚未初始化的值,对吧?
  • OP 对他们的构造函数做了什么。当然,当编译器在幕后魔术时,它是正式的左值到右值转换吗?我不确定。

标签: c++ return-value-optimization


【解决方案1】:

你的比较不公平。


struct S
{
    S() {}
    float vals[64];
};

有了这个,你实际上并没有初始化vals。为了公平起见,应该是:

struct S
{
    S() : vals{} {}
    float vals[64];
};

或者干脆使用:

S() = default;

现在 gcc 和 clang 都为它们生成相同的代码,而 msvc 为 makeS 生成相同的代码:https://godbolt.org/z/rezxTorGs


还有人请纠正我:

struct S
{
    float vals[64];
};

inline S makeS() { return {}; }

我相信这个使用默认构造函数而不是聚合初始化。

您可能需要使用return {{}};return {.vals = {}}; 来强制聚合初始化。

【讨论】:

  • 仍然是聚合初始化。没有构造函数调用。
猜你喜欢
  • 2010-09-11
  • 1970-01-01
  • 2012-10-17
  • 1970-01-01
  • 2010-12-20
  • 1970-01-01
  • 1970-01-01
  • 2016-12-20
相关资源
最近更新 更多