【发布时间】:2012-11-03 07:11:54
【问题描述】:
当在std::vector 中存储大量 个自定义类(不是“简单”类,例如不是std::string,不是std::complex 等)的实例时,我们应该选择简单的std::vector<X>,还是std::vector<std::unique_ptr<X>> 更好?
我编写了一些基准代码(从 this blog post 扩展代码,关于 C++03 上的 C++11 移动语义改进),似乎 vector<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>>;而在 C++11 中,启用移动语义的 std::unique_ptr 似乎提供了最好的结果。
我在这里遗漏了什么吗?这是一个很好的 C++ 指南吗?在大 vectors 中存储(智能)指针指向类实例比类实例本身更好?
基准代码如下:
////////////////////////////////////////////////////////////////////////////////
//
// 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;
}
}
////////////////////////////////////////////////////////////////////////////////
【问题讨论】:
-
在可能的情况下,
reserve向量中需要的空间通常更简单,或者使用deque而不是vector。可能值得将这两个选项添加到您的基准中进行比较,但对于特定的用例,它们中的任何一个都可能是不可能的。我认为deque可能比vector<unique_ptr>的破坏性更小。也可以看boost:stable_vector。 -
我想性能是一样的,但你试过
MyObject(MyObject&& src) = default看看内置的移动构造函数是否有效?我没有看到任何需要手动移动东西的特殊内存管理标准。 -
另外,当您说“禁止复制”时,只需
= delete那些方法。例如。Stopwatch(const Stopwatch&) = delete;等 -
@Sean:我正在使用 VC10 (VS2010 SP1),不幸的是它支持非常原始的 C++11;默认和删除函数aren't available 即使在 VC11 (VS2012) 中也是如此。
-
@Mr.C64 Drat,我担心你会说这样的话。此外,如果您想比较移动语义,您应该将复制构造函数作为基线。与普通的复制 ctor 相比,Move 非常好。
标签: c++ performance vector c++11 move-semantics