【问题标题】:Can creation of composite objects from temporaries be optimised away?可以优化从临时对象创建复合对象吗?
【发布时间】:2011-08-17 11:28:13
【问题描述】:

我已经就这个问题提出了一些涉及到的问题,但我得到了不同的回答,所以我认为最好直接问。

假设我们有以下代码:

// Silly examples of A and B, don't take so seriously, 
// just keep in mind they're big and not dynamically allocated.
struct A { int x[1000]; A() { for (int i = 0; i != 1000; ++i) { x[i] = i * 2; } };
struct B { int y[1000]; B() { for (int i = 0; i != 1000; ++i) { y[i] = i * 3; } };

struct C
{
  A a;
  B b;
};

A create_a() { return A(); }
B create_b() { return B(); }

C create_c(A&& a, B&& b)
{
  C c;
  c.a = std::move(a);
  c.b = std::move(b);
  return C; 
};

int main()
{
  C x = create_c(create_a(), create_b());
}

现在理想情况下,create_c(A&&, B&&) 应该是空操作。与创建 A 和 B 并在堆栈上传递对它们的引用的调用约定不同,应该创建 A 和 B 并按值传递,而不是返回值 c。使用 NRVO,这意味着直接创建它们并将它们传递给 x,而函数 create_c 不需要做进一步的工作。

这样可以避免创建 A 和 B 的副本。

有没有办法允许/鼓励/强制编译器的这种行为,或者优化编译器通常会这样做吗?这仅在编译器内联函数时才有效,还是跨编译单元有效。

(我认为这可以跨编译单元工作...)

如果create_a()create_b() 带了一个隐藏参数来放置返回值,他们可以直接将结果放入x,然后通过引用传递给create_c(),不需要做任何事情并且立即返回。

【问题讨论】:

    标签: c++ optimization c++11


    【解决方案1】:

    给C一个构造函数然后说:

    C create_c(const A & a, const B & b)
    {
      return C( a, b );
    }
    

    它有很多优化的可能性。或者确实摆脱了创建功能。我不认为这是一个很好的激励例子。

    【讨论】:

    • @unapersson:这如何消除 A 和 B 的副本。您能详细说明一下吗?
    • @Clinton 你不能消除 A 和 B 的所有副本,因为 C 最终必须包含副本。
    • @unapersson :不,在克林顿显示的代码中,有 0 个副本。 RVO 消除了从create_acreate_b 返回的副本,create_c 采用右值引用并将它们移动到它的C 实例中。此示例中唯一不必要的低效率是 C 默认构造 AB 的实例然后移动它们,而不是首先使用移动语义来初始化它们。
    • @ildjam:真的把ab移动c吗? C++0x 对我来说仍然是新的,但我的猜测是没有可以执行的合理廉价 move 操作,因为数组本身不能从一个 move位置到另一个。此示例不会将 create_acreate_b 的副本删除到新的 C 对象中,但原始代码也不会(除非代码也被内联,然后有更大的优化潜力)。
    • @ildjam - 好吧,我不是未来的证明,我很瘦。 :-)
    【解决方案2】:

    有两种优化可以应用于您的情况:

    1. Function Inlining(对于 A、B 和 C(以及其中包含的 A 和 B))
    2. Copy elision(仅限 C(以及它包含的 A 和 B),因为您按值返回了 C)

    对于这么小的函数,它可能会被内联。如果它存在于同一个翻译单元中,大多数编译器都会这样做,并且像 MSVC++ 和 G++ 这样好的编译器(我认为是 LLVM,但我不确定那个)具有整个程序优化设置,它甚至可以跨越翻译单位。如果函数是内联的,那么是的,函数调用(以及它附带的副本)根本不会发生。

    如果由于某种原因该函数没有被内联(即您在 MSVC++ 上使用了__declspec(noinline)),那么您仍然有资格使用Named Return Value Optimization (NRO),这是好的 C++ 编译器(同样,MSVC++、G++ ,我认为 LLVM)都实现了。基本上,该标准规定,如果编译器可以避免这样做,则允许编译器在返回时不执行复制,并且他们通常会发出避免它的代码。您可以采取一些措施来停用 NRVO,但在大多数情况下,它是一种非常安全的优化方式。

    最后,简介。如果您发现性能问题,请找出其他问题。否则,当且仅当您需要时,我会以理想的方式编写内容并用性能更高的构造替换它们。

    【讨论】:

      【解决方案3】:

      有不同的方法可以优化您拥有的代码,但右值引用不是一种。问题是AB 都不能免费移动,因为你不能窃取对象的内容。考虑以下示例:

      template <typename T>
      class simple_vector {
         typedef T element_type;
         typedef element_type* pointer_type;
         pointer_type first, last, end_storage;
      public:
         simple_vector() : first(), last(), end_storage() {}
         simple_vector( simple_vector const & rhs )              // not production ready, memory can leak from here!
            : first( new element_type[ rhs.last - rhs.first ] ),
              last( first + rhs.last-rhs.first ),
              end_storage( last )
         {
             std::copy( rhs.first, rhs.last, first );
         }
         simple_vector( simple_vector && rhs ) // we can move!
            : first( rhs.first ), last( rhs.last ), end_storage( rhs.end_storage )
         {
            rhs.first = rhs.last = rhs.end_storage = 0;
         }
         ~simple_vector() {
            delete [] rhs.first;
         }
         // rest of operations
      };
      

      在这个例子中,由于资源是通过指针来保存的,所以有一种简单的移动对象的方法(即将旧对象的内容窃取到新对象中,并将旧对象留在一个可破坏但无用的状态。只需复制指针并将旧对象中的指针重置为 null,这样原始对象的析构函数就不会释放内存。

      AB 的问题在于,实际内存通过数组对象中保存,并且该数组不能移动到新 C 对象的不同内存位置。

      当然,由于您在代码中使用堆栈分配的对象,编译器可以使用旧的(N)RVO,并且当您这样做时:C c = { create_a(), create_b() };编译器可以执行该优化(基本上设置属性@ 987654328@ 在从create_a 返回的对象的地址上,而在编译create_a 时,直接在同一地址上创建返回的临时对象,如此有效,c.a,从create_a 返回的对象和内部构造的临时对象create_a(对构造函数隐含this同一个对象,避免了两个副本。c.b 也可以这样做,避免了复制成本。如果编译器确实内联您的代码,它将删除 create_c 并将其替换为类似于:C c = {create_a(), create_b()}; 的构造,因此它可以优化所有副本。

      另一方面请注意,这种优化不能完全用于C 对象动态分配的情况,如C* p = new C; p-&gt;a = create_a();,因为目标不在堆栈中,编译器只能优化 create_a 内部的临时值及其返回值,但不能使其与 p-&gt;a 一致,因此需要进行复制。这是 rvalue-references 优于 (N)RVO 的优势,但如前所述,您不能直接在代码示例中有效地使用 rvalue-references

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-10-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多