复制和交换习语的效率低下?

Mr.*_*C64 10 c++ performance vector copy-and-swap c++11

我正在测试一些代码,其中std::vector一个类中有一个数据成员.类是既可以复印和活动,并operator=描述实现这里使用复制和交换成语.

如果有两个vectors,比如v1大容量和v2小容量,并被v2复制到v1(v1 = v2),则v1在分配后保留大容量 ; 这是有道理的,因为下一次v1.push_back()调用不必强制新的重新分配(换句话说:释放已经可用的内存,然后重新分配它来增长向量没有多大意义).

但是,如果对具有as数据成员的类进行相同的赋值vector,则行为不同,并且在赋值之后不保留更大的容量.

如果不使用复制operator=和交换习惯用法,并且复制和移动operator=是分开实现的,那么行为就像预期的那样(对于普通的非成员vectors).

这是为什么?我们是否应该遵循复制和交换习惯用法,而是分别实施operator=(const X& other)(复制 op=)和operator=(X&& other)(移动 op=)以获得最佳性能?

这是一个可重复的测试输出与复制和交换成语(注意如何在这种情况下,之后 x1 = x2,x1.GetV().capacity()就是1000,不1,000,000):

C:\TEMP\CppTests>cl /EHsc /W4 /nologo /DTEST_COPY_AND_SWAP test.cpp
test.cpp

C:\TEMP\CppTests>test.exe
v1.capacity() = 1000000
v2.capacity() = 1000

After copy v1 = v2:
v1.capacity() = 1000000
v2.capacity() = 1000

[Copy-and-swap]

x1.GetV().capacity() = 1000000
x2.GetV().capacity() = 1000

After x1 = x2:
x1.GetV().capacity() = 1000
x2.GetV().capacity() = 1000
Run Code Online (Sandbox Code Playgroud)

这是没有复制和交换习惯用法的输出(注意在这种情况下x1.GetV().capacity() = 1000000,如预期的那样):

C:\TEMP\CppTests>cl /EHsc /W4 /nologo test.cpp
test.cpp

C:\TEMP\CppTests>test.exe
v1.capacity() = 1000000
v2.capacity() = 1000

After copy v1 = v2:
v1.capacity() = 1000000
v2.capacity() = 1000

[Copy-op= and move-op=]

x1.GetV().capacity() = 1000000
x2.GetV().capacity() = 1000

After x1 = x2:
x1.GetV().capacity() = 1000000
x2.GetV().capacity() = 1000
Run Code Online (Sandbox Code Playgroud)

随后是可编译的示例代码(使用VS2010 SP1/VC10进行测试):

#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;

class X
{
public:
    X()
    {
    }

    explicit X(const size_t initialCapacity)
    {
        m_v.reserve(initialCapacity);
    }

    X(const X& other)
        : m_v(other.m_v)
    {
    }

    X(X&& other)
        : m_v(move(other.m_v))
    {
    }

    void SetV(const vector<double>& v)
    {
        m_v = v;
    }

    const vector<double>& GetV() const
    {
        return m_v;
    }

#ifdef TEST_COPY_AND_SWAP     
    //
    // Implement a unified op= with copy-and-swap idiom.
    //

    X& operator=(X other)
    {
        swap(*this, other);       
        return *this;
    }

    friend void swap(X& lhs, X& rhs)
    {
        using std::swap;

        swap(lhs.m_v, rhs.m_v);
    }    
#else    
    //
    // Implement copy op= and move op= separately.
    //

    X& operator=(const X& other)
    {
        if (this != &other)
        {
            m_v = other.m_v;
        }
        return *this;
    }

    X& operator=(X&& other)
    {
        if (this != &other)
        {
            m_v = move(other.m_v);
        }
        return *this;
    }    
#endif

private:
    vector<double> m_v;
};    

// Test vector assignment from a small vector to a vector with big capacity.
void Test1()
{
    vector<double> v1;
    v1.reserve(1000*1000);

    vector<double> v2(1000);

    cout << "v1.capacity() = " << v1.capacity() << '\n';
    cout << "v2.capacity() = " << v2.capacity() << '\n';

    v1 = v2;
    cout << "\nAfter copy v1 = v2:\n";    
    cout << "v1.capacity() = " << v1.capacity() << '\n';
    cout << "v2.capacity() = " << v2.capacity() << '\n';
}    

// Similar to Test1, but now vector is a data member inside a class.
void Test2()
{
#ifdef TEST_COPY_AND_SWAP 
    cout << "[Copy-and-swap]\n\n";
#else
    cout << "[Copy-op= and move-op=]\n\n";
#endif

    X x1(1000*1000);

    vector<double> v2(1000);
    X x2;
    x2.SetV(v2);

    cout << "x1.GetV().capacity() = " << x1.GetV().capacity() << '\n';
    cout << "x2.GetV().capacity() = " << x2.GetV().capacity() << '\n';

    x1 = x2;
    cout << "\nAfter x1 = x2:\n";
    cout << "x1.GetV().capacity() = " << x1.GetV().capacity() << '\n';
    cout << "x2.GetV().capacity() = " << x2.GetV().capacity() << '\n';
}

int main()
{
    Test1();       
    cout << '\n';    
    Test2();
}
Run Code Online (Sandbox Code Playgroud)

Pup*_*ppy 12

使用a std::vector进行复制和交换确实会导致性能下降.这里的主要问题是复制std::vector涉及两个不同的阶段:

  1. 分配新的内存部分
  2. 复制东西.

复制和交换可以消除#2但不会消除#1.考虑在swap()调用之前您将观察到的内容,但是在输入赋值操作之后.你有三个向量 - 即将要被覆盖的向量,一个是副本的向量,以及原始参数.

这清楚地暗示,如果即将被覆盖的矢量具有足够或过剩的容量,则在创建中间矢量时存在浪费,并且源的额外容量损失.其他容器也可以这样做.

复制和交换是一个很好的基准,特别是在涉及异常安全时,但它并不是全球性能最高的解决方案.如果你处于一个狭窄的区域,那么其他更专业的实现可以更有效 - 但要注意,在这个领域的异常安全是非平凡的,如果不进行复制和交换有时是不可能的.


小智 5

在这种X情况下,您正在交换向量,而不是使用vector::operator=().作业保留了容量.swap交换能力.