c – 我们是否应该将智能指针存储到大型std :: vector中的类实例中以获得更好的性能?

当在std :: vector中存储大量自定义类的实例(不是“简单”类,例如不是std :: string,而不是std :: complex等)时,我们应该选择一个简单的std :: vector< X>,或者是std :: vector< std :: unique_ptr< X>>更好的选择?

我写了一些基准代码(从this blog post扩展代码关于C 03上的C 11移动语义改进),似乎向量< unique_ptr< X>>为1,500,000个项目向量提供更好的性能.事实上,在装有Windows 7 64位,Intel Core i5四核CPU和8 GB RAM的PC上,我得到了以下结果(test.exe 1500):

> vector< unique_ptr< MyObject>>:1.5秒
> vector< shared_ptr< MyObject>>:1.6秒
> vector< MyObject>:1.8秒

因此,在C 03(其中std :: unique_ptr不可用)中,似乎最佳选择是vector< shared_ptr< X>&gt ;;而在C 11中,支持move-semantics的std :: unique_ptr似乎提供了最好的结果.

我在这里错过了什么吗?这是一个很好的C指南,在大向量中,最好将(智能)指针存储到类实例而不是类实例本身吗?

基准代码如下:

////////////////////////////////////////////////////////////////////////////////
//
// Test vector<X> vs. vector<unique_ptr<X>> vs. vector<shared_ptr<X>>.
//
// Original benchmark code from:
//   http://blogs.msdn.com/b/vcblog/archive/2009/06/23/stl-performance.aspx
//
////////////////////////////////////////////////////////////////////////////////


#include <exception>    // std::invalid_argument
#include <iostream>     // std::cout
#include <memory>       // std::shared_ptr, std::unique_ptr
#include <ostream>      // std::endl
#include <stdexcept>    // std::exception
#include <string>       // std::wstring
#include <utility>      // std::move
#include <vector>       // std::vector

#include <Windows.h>    // Win32 Platform SDK (high performance counters, etc.)

using namespace std;


// Measure time.
class Stopwatch
{
public:

    Stopwatch()
        : m_start(0),
          m_finish(0)
    {
    }

    static void PerfStartup()
    {
        // to confine the test to run on a single processor 
        // in order to get consistent results for all tests.
        SetThreadAffinityMask(GetCurrentThread(), 1);
        SetThreadIdealProcessor(GetCurrentThread(), 0);
        Sleep(1);
    }

    void Start()
    {
        m_finish = 0;
        m_start = Counter();
    }

    void Stop()
    {
        m_finish = Counter();
    }

    // Elapsed time, in seconds
    double ElapsedTime() const
    {
        return (m_finish - m_start) * 1.0 / Frequency();
    }

    void Reset()
    {
        m_start = m_finish = 0;
    }


private:
    long long m_start;
    long long m_finish;

    static long long Counter() 
    {
        LARGE_INTEGER li;
        QueryPerformanceCounter(&li);
        return li.QuadPart;
    }

    static long long Frequency() 
    {
        LARGE_INTEGER li;
        QueryPerformanceFrequency(&li);
        return li.QuadPart;
    }


// Ban copy
private:
    Stopwatch(const Stopwatch&);
    Stopwatch& operator=(const Stopwatch&);
};


// Measure execution time of a block of code.
class ScopedStopwatch
{
public:

    ScopedStopwatch()
    {
        m_sw.Start();
    }

    ~ScopedStopwatch()
    {
        m_sw.Stop();
        cout << "Elapsed time: " << m_sw.ElapsedTime() << " sec" << endl;
    }

private:
    Stopwatch m_sw;

    ScopedStopwatch(const ScopedStopwatch&);
    ScopedStopwatch& operator=(const ScopedStopwatch&);
};


// User Defined Type
class MyObject
{
public:
    wstring name;
    wstring address;
    wstring telephone;
    wstring name2;
    wstring address2;
    wstring telephone2;

    // Default constructor
    MyObject()
    {
    }

    // Copy Constructor
    MyObject(const MyObject& other)
        : name(other.name),
          telephone(other.telephone),
          address(other.address),
          name2(other.name2),
          telephone2(other.telephone2),
          address2(other.address2)
    {
    }

    // Copy assignment operator
    MyObject& operator=(const MyObject& other)
    {
        if (this != &other)
        {
            name = other.name;
            telephone = other.telephone;
            address = other.address;
            name2 = other.name2;
            telephone2 = other.telephone2;
            address2 = other.address2;
        }

        return *this;
    }

    // Move constructor
    MyObject(MyObject&& other)
        : name(move(other.name)),
          telephone(move(other.telephone)),
          address(move(other.address)),
          name2(move(other.name2)),
          telephone2(move(other.telephone2)),
          address2(move(other.address2))
    {
    }

    // Move assignment operator
    MyObject& operator=(MyObject&& other)
    {
        if (this != &other)
        {
            name = move(other.name);
            telephone = move(other.telephone);
            address = move(other.address);
            name2 = move(other.name2);
            telephone2 = move(other.telephone2);
            address2 = move(other.address2);
        }

        return *this;
    }
};


MyObject MakeTestObject()
{
    MyObject obj;
    obj.name = L"Stephan T. Lavavej Stephan T. Lavavej Stephan T. Lavavej";
    obj.telephone = L"314159265 314159265 314159265 314159265 314159265";
    obj.address = L"127.0.0.0 127.0.0.0 127.0.0.0 127.0.0.0 127.0.0.0 127.0.0.0";
    obj.name2 = L"Mohammad Usman. Mohammad Usman. Mohammad Usman. ";
    obj.telephone2 = L"1234567890 1234567890 1234567890 1234567890 1234567890";
    obj.address2 = L"Republik Of mancunia. Republik Of mancunia Republik Of mancunia";

    return obj;
}


unique_ptr<MyObject> MakeUniqueTestObject()
{
    unique_ptr<MyObject> obj( new MyObject() );
    obj->name = L"Stephan T. Lavavej Stephan T. Lavavej Stephan T. Lavavej";
    obj->telephone = L"314159265 314159265 314159265 314159265 314159265";
    obj->address = L"127.0.0.0 127.0.0.0 127.0.0.0 127.0.0.0 127.0.0.0 127.0.0.0";
    obj->name2 = L"Mohammad Usman. Mohammad Usman. Mohammad Usman. ";
    obj->telephone2 = L"1234567890 1234567890 1234567890 1234567890 1234567890";
    obj->address2 = L"Republik Of mancunia. Republik Of mancunia Republik Of mancunia";

    return obj;
}


shared_ptr<MyObject> MakeSharedTestObject()

{    
    auto obj = make_shared<MyObject>();
    obj->name = L"Stephan T. Lavavej Stephan T. Lavavej Stephan T. Lavavej";
    obj->telephone = L"314159265 314159265 314159265 314159265 314159265";
    obj->address = L"127.0.0.0 127.0.0.0 127.0.0.0 127.0.0.0 127.0.0.0 127.0.0.0";
    obj->name2 = L"Mohammad Usman. Mohammad Usman. Mohammad Usman. ";
    obj->telephone2 = L"1234567890 1234567890 1234567890 1234567890 1234567890";
    obj->address2 = L"Republik Of mancunia. Republik Of mancunia Republik Of mancunia";

    return obj;
}


void Test(int count)
{
    Stopwatch::PerfStartup();

    cout << "Inserting " << count << " items in vector.\n";


    cout << "\nTesting vector<MyObject>\n";
    {
        ScopedStopwatch sw;

        vector<MyObject> v;
        for (int i = 0; i < count; i++)
        {
            v.push_back(MakeTestObject());
        }
    }


    cout << "\nTesting vector<unique_ptr<MyObject>>\n";
    {
        ScopedStopwatch sw;

        vector<unique_ptr<MyObject>> v;
        for (int i = 0; i < count; i++)
        {
            v.push_back(MakeUniqueTestObject());
        }
    }


    cout << "\nTesting vector<shared_ptr<MyObject>>\n";
    {
        ScopedStopwatch sw;

        vector<shared_ptr<MyObject>> v;
        for (int i = 0; i < count; i++)
        {
            v.push_back(MakeSharedTestObject());
        }
    }
}


int main(int argc, char * argv[])
{
    static const int kExitOk = 0;
    static const int kExitError = 1;

    try
    {
        if (argc != 2)
        {
            throw invalid_argument("Bad syntax. Pass insertion count (x 1,000).");
        }

        const int countK = atoi(argv[1]);
        Test(countK * 1000);

        return kExitOk;
    }
    catch (const exception & e)   
    {
        cerr << "*** ERROR: " << e.what() << endl;
        return kExitError;
    }
}

////////////////////////////////////////////////////////////////////////////////
最佳答案
在C 11中,如果您没有使用启用移动的对象,那么您应该使用std :: unique_ptr< T>的向量.来自#include< memory>.的std ::的unique_ptr< T>重量较轻,具有与std :: shared_ptr< T>相似的语义.但在一个重要领域有所不同:对象所有权是明确的.在vector的情况下,向量拥有它包含的对象.现在,如果您正在使用启用移动的对象,只需使用对象的向量,因为它通常“足够快”. C 11中所有启用STL的容器都使用了移动语义(即是的,稍慢,但你在生产力方面获益).如果性能是一个问题,你可以回到std :: unqiue_ptr< T>原因如下.

如果您使用的是pre-C 11,则boost :: shared_ptr< T>并不是你可以做的更糟糕的事情,并且可能是一个适当的过渡路径,直到std :: unique_ptr< T>变得可用.使用boost :: shared_ptr< T>涉及原子增量和指针的赋值.两者都比std :: unique_ptr< T>相当便宜,但更昂贵(和不同的语义).

Move构造函数比移动std :: unique_ptr< T>更昂贵并不让我感到惊讶.因为移动构造函数仍在分配一个对象(即使它的内容/内容被借用,移动,重新定位),而移动一个std :: unique_ptr< T>只是一个整数/指针赋值.使用jemalloc(3)可能会降低Move构造函数的成本,但这只能在* NIX平台上使用.

由于最后一点,基准并不完全是苹果对苹果.如果您正在寻找一致的性能,std :: unique_ptr< T>可能是要走的路(没有分配),但是如果你正在寻找一种“原生”开发习语,这有助于简化开发方法,其中性能不是最重要的方面(即生产力比性能更重要),那么与移动构造函数一起使用普通对象.

转载注明原文:c – 我们是否应该将智能指针存储到大型std :: vector中的类实例中以获得更好的性能? - 代码日志