【问题标题】:C++ Vector: push_back Objects vs push_back Pointers performanceC++ 向量:push_back 对象与 push_back 指针性能
【发布时间】:2014-09-13 20:13:50
【问题描述】:

我正在测试推回对象推回对象指针到 C++ 中的向量之间的性能差异。

我在 Stackoverflow 和其他文章中读到,除非你必须这样做,否则你应该避免推回指针......

但是,我意识到推回指针会带来巨大的性能提升,,,

这是我运行的一个简单测试:

tstart = chrono::system_clock::now();

vector<MyObject> VectorOfObjects;
for (int i=0; i<10000; i++) {
    MyObject x("test");
    VectorOfObjects.push_back(x);
}

tend = chrono::system_clock::now();
tt = tend-tstart;
cout << "Pushback Object: " << tt.count()*1000 << " Milliseconds\n" << endl;




tstart = chrono::system_clock::now();

vector<MyObject *> VectorOfPointers;
for (int i=0; i<10000; i++) {
    VectorOfPointers.push_back(new MyObject("test"));
}

tend = chrono::system_clock::now();
tt = tend-tstart;
cout << "Pushback Pointers: " << tt.count()*1000 << " Milliseconds\n" << endl;


结果实际上非常令人惊讶:

Pushback Objects:  989 Milliseconds
Pushback Pointers: 280 Milliseconds


如您所见,推回指针比推回对象快 3~4 倍!这是一个巨大的性能差异,尤其是在处理大量数据时。

所以我的问题是:为什么不使用指针向量??

Stackoverflow 上几乎所有关于类似问题的帖子的答案都是避免使用指针向量。

我知道内存泄漏可能是个问题,但我们总是可以使用智能指针,甚至在销毁时手动删除指针也不是那么困难..

我也很好奇造成这种性能差异的原因..

谢谢


更新:

实际上我在ideone .... 上进行了测试,在这里,推回对象更快!!!

在 Visual Studio 中,推回 Objects 会更慢..

这是为什么……??

【问题讨论】:

  • 如果您需要指针向量,请使用 Boost ptr 容器。 C++11 的移动语义应该很好地涵盖对象的副本。如果您要向我们提供性能示例,请实际使用完整的示例和编译器选项。
  • 通常情况下,容器中的对象(以及一般的对象)的访问频率远高于创建它们的频率。您是否尝试过衡量其成本?
  • 你是如何构建这个的?编译器应该已经优化了第一个循环。
  • @juanchopanza 抱歉,“运行循环没有影响”??它正在改变向量的状态。它也不应该完全删除代码。不知道你的意思是什么
  • 我是认真的。最重要的是,对new 的 10000 次调用非常昂贵。对象向量只分配少量内存,您可以通过在填充之前调用VectorOfObjects.reserve(10000) 将其减少到两个。最重要的是,对象数据的向量将是连续的,而指针是分散的。访问前者会更快。

标签: c++ pointers vector


【解决方案1】:

公平地说,当measuring 您的代码时,您应该考虑所有这些指针的释放。示例代码如下:

#include <chrono>
#include <string>
#include <iostream>
#include <functional>
#include <vector>

using namespace std;

// 1. A way to easily measure elapsed time -------------------
template<typename TimeT = std::chrono::milliseconds>
struct measure
{
    template<typename F>
    static typename TimeT::rep execution(F const &func)
    {
        auto start = std::chrono::system_clock::now();
        func();
        auto duration = std::chrono::duration_cast< TimeT>(
            std::chrono::system_clock::now() - start);
        return duration.count();
    }
};
// -----------------------------------------------------------


// 2. MyObject -----------------------------------------------
struct MyObject {
    string mem;
    MyObject(const char *text) : mem(text) {};
};
// -----------------------------------------------------------


int main() 
{
    vector<MyObject> VectorOfObjects;
    vector<MyObject *> VectorOfPointers;

    cout << "Pushback Object: " << measure<>::execution([&]()
    {
        for (int i = 0; i < 100000; i++) {
            MyObject x("test");
            VectorOfObjects.push_back(x);
        }
    }) << endl;


    cout << "Pushback Pointers: " << measure<>::execution([&]()
    {
        for (int i = 0; i < 100000; i++) 
            VectorOfPointers.push_back(new MyObject("test"));
        for (auto &item : VectorOfPointers) 
            delete item;
    }) << endl;

    return 0;
}

当使用

编译时

g++ -std=c++11 -O3 -march=native -Wall -pedantic

结果是(我在 for 循环中使用 +1 个数量级):

回推对象:20

后推指针:32


  • 如果你用过

    VectorOfObjects.emplace_back("test");

    VectorOfObjects 修改的持续时间将下降到 18

  • 如果你preallocated两个向量

    vector<MyObject> VectorOfObjects;
    VectorOfObjects.reserve(100000);
    
    vector<MyObject *> VectorOfPointers;
    VectorOfPointers.reserve(100000);
    

    结果将是17-34(再次用于对象向量)

  • 如果你use a vector of unique pointers那么结果是相似的

    vector<unique_ptr<MyObject>> VectorOfPointers;
    

    请注意,我正在限制向量的范围以明确说明智能指针的破坏

  • 其他选择包括 boost 的 pointer containers,在这种情况下,相关的数据结构将是 pointer vector

【讨论】:

  • 感谢并尊重重新运行我的测试 :)
  • 但是我不明白为什么我在 Visual Studio 中运行测试时得到了这个结果....你知道我如何在 VS 中检查我的编译器选项,以及为什么这会影响性能..?
  • 这个答案不是很好:要么使用emplace,要么使用共享指针。在 STL 容器中使用裸指针在 C++11 中被认为是不好的风格,使用 delete 修复了内存泄漏,但并没有改变这不是要走的路的事实。
  • @Fozi 实际上,我在 Stackoverflow 上就该问题提出了另一个问题。如果简单地删除可以解决内存问题,为什么使用裸指针不好?我的意思是在某些情况下,除了使用原始指针之外别无选择。例如,covariant return types... 是否有文章解释了为什么将原始指针存储在 STL 容器中不好?
  • @user2436815:该行为可能是由 VS 没有 noexcept 说明符这一事实引起的,因此可能假设移动抛出,这意味着矢量调整大小为了异常安全,将深拷贝而不是简单地移动。如果是这样,如果可以说服 VS 移动分配/构造函数不会抛出,那可能会对性能产生重大影响。
【解决方案2】:

与常规指针相反,我更喜欢使用共享指针,并且我总是尽可能使用它们。

在处理变化很大的向量时,我将共享指针与向量一起使用。

在处理向量时应避免使用常规指针,因为它们需要手动销毁,只会导致内存泄漏。

所以回答你的问题...

查看shared_ptr 库并使用它们,这里有一个链接http://www.cplusplus.com/reference/memory/shared_ptr/

希望这能回答你的问题

【讨论】:

  • 默认使用的智能指针应该是std::unique_ptr
【解决方案3】:

您的示例代码的编写方式肯定存在内存泄漏问题。同意您可以通过删除来解决该问题。

可以,但是很麻烦。如果你处理好内存泄漏之类的事情,那就没问题了。

这里性能的根本问题是正在制作对象的副本。你创建一个对象。当您将其添加到矢量时。它创建一个新对象并使用复制构造函数复制您的对象。

C++11 通过引入 emplace_back() 稍微改善了这种情况。所以如果你使用的是 C++11,使用 emplace 或许可以得到同样的性能。

【讨论】:

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