C++ - vector指针对象的性能与对象性能比较

8
在这种情况下,问题场景是一款游戏,因此所有资源都在开始时分配,然后在某个级别上进行迭代。存储在向量中的对象是复杂类的实例,当然,在加载时将它们复制到向量中实际上是耗时的,但并不重要。但如果我的主要关注点是在运行时迭代类对象的速度,那么我是否应该将类对象本身存储在向量中,而不仅仅是指向类对象的指针,正如传统建议的那样?在这个例子中,我不担心内存管理,只关注迭代速度。

6
如果你正在讨论已建立的向量,性能差异将会微小,但是“可能”会更青睐于对象向量,因为一个间接层级被消除了。一如既往地,“对两者进行测试分析”,亲自看一看。 - WhozCraig
1
但是,在对象向量场景中,数据连续不会自然地导致更快的迭代吗? - metamorphosis
1
它们都是相同类型吗?如果不是,那么您必须使用指针来避免切片。 - Retired Ninja
2
@metamorphosis 其他原因之外,是的。但很有可能你的“对象”也有自己的动态成员(大多数都有;字符串、其他向量等)。如果它是真正的 POD 类型,并且足够小以在数据缓存中具有良好的负载,则毫无疑问,对象向量将表现更好,假设我们正在谈论一个已经构建好的向量。但像我说的那样,对其进行分析 - WhozCraig
2
还要注意,如果这是一个问题,指针数组可以通过为向量提供自定义分配器来使其更加缓存友好,以便从连续内存的内存池中分配对象。 - Peter Clark
显示剩余2条评论
3个回答

9
我虽然晚了些回答这个问题,但性能方面很重要,目前网上的答案纯粹是理论性的和/或专注于内存管理方面。因此,我最近尝试了三种相关情况的一些实际基准测试信息。你的结果可能不同,但至少有一些关于实际应用程序中事物如何发展的想法。
这里引用的类A大约有10个成员字段,其中一半是原语类型,另一半是std::string、std::vector和其他动态大小的容器。该应用程序已经相当优化,因此我们想看看现在哪种架构可以为我们提供最快的循环遍历A的集合。任何A对象的成员字段的值都可能在应用程序生命周期内发生变化,但向量中A对象的数量在我们执行的许多重复迭代中不会改变(这种持续迭代占该应用程序执行时间的约95%)。在所有情况下,循环都是使用典型的std::iterator或std::const_iterator执行的。每个枚举的A对象至少访问了几个成员字段。
场景1 - 对象指针的向量
尽管最简单,但是 std::vector<A*> 这种架构比其他架构稍微慢了一些。
方案2 — 对象指针的向量,使用放置 new 分配对象
这种方法背后的想法是通过强制将对象分配到连续的内存空间中来改善缓存的局部性。因此,对象指针的 std::vector<A*> 由于 std::vector 实现而保证是连续的,而且因为我们使用了放置 new 惯用语,A 对象本身也将在堆上连续。我使用了与此 答案 中概述的相同方法;有关放置 new 的更多信息可以在 这里 找到。
这个场景比场景1快了2.7%。
方案3 — 对象的向量

在这里,我们直接使用std::vector<A>std::vector的实现保证了我们的A对象在内存中是连续的。请注意,对象的std::vector涉及到A的移动和复制构造函数的考虑。为避免不必要的移动和/或重建,最好提前std::vector.reserve()最大可能需要的大小,然后尽可能使用std::vector.emplace_back()(而不是push_back())。循环遍历此结构最快,因为我们能够消除一级指针间接。

这种方法比方案1快6.4%。

与另一个问题相关的answer也显示出普通对象(作为类成员)比相应的指针(作为类成员)更快。


1
首先,指针应该用于存储大块的内容。因为如果使用对象数组,每次存储时都会创建n个庞大的对象并复制每个对象(这也是一个很大的代价)。 其次,如果您正在使用向量(STL),则向量大小每次填满内存时都会增长。 主要的成本是将第一个数据复制到第二个数据中,这实际上是主要的成本,即复制。 此外,如果使用内置,则所需的最小成本是此成本。

1
这并没有回答我的问题。这是“标准”答案,与内存管理有关,但与迭代速度无关。 - metamorphosis
如果您正在使用向量(STL),则向量的大小会在其内存满时每次增加两倍。但我不知道您从哪里得到这个信息,因为标准并没有指定任何类似的规定。实现可以有任何重新分配和选择新更大大小的算法。 - Shoe
你是对的,这是我的错误,它不会每次增长两倍。 - Gauri

1
不,她没有错,她绝对正确,尽管你只询问了快速迭代的问题,但这与内存有很大关联...内存越多,访问速度就会变慢...
我有一个实时演示...
#include <iostream>
#include <string>
#include <vector>
#include "CHRTimer.h"

struct Items
{
    std::string name;
    int id;
    float value;
    float quantity;
};

void main()
{

    std::vector<Items> vecItems1;
    for(int i = 0; i < 10000; i++)
    {
        Items newItem;
        newItem.name = "Testing";
        newItem.id = i + 1;
        newItem.value = 10.00;
        newItem.quantity = 1.00;

        vecItems1.push_back(newItem);
    }

    CHRTimer g_timer;
    g_timer.Reset();
    g_timer.Start();
    for(int i = 0; i < 10000; i++)
    {
        Items currentItem = vecItems1[i];
    }
    g_timer.Stop();
    float elapsedTime1 = g_timer.GetElapsedSeconds();
    std::cout << "Time Taken to load Info from Vector of 10000 Objects -> " << elapsedTime1 << std::endl;

    std::vector<Items*> vecItems;
    for(int i = 0; i < 100000; i++)
    {
        Items *newItem = new Items();
        newItem->name = "Testing";
        newItem->id = i + 1;
        newItem->value = 10.00;
        newItem->quantity = 1.00;

        vecItems.push_back(newItem);
    }

    g_timer.Reset();
    g_timer.Start();
    for(int i = 0; i < 100000; i++)
    {
        Items *currentItem = vecItems[i];
    }
    g_timer.Stop();
    float elapsedTime = g_timer.GetElapsedSeconds();
    std::cout << "\nTime Taken to load Info from Vector of 100000 pointers of Objects -> " << elapsedTime;
}

第一个案例是使用STL向量存储对象,仅进行了10000次迭代,但仍需要0.0269秒。 - Kamal Singla
第二个是10倍,即100000次迭代,但仍然只需要0.005秒。 - Kamal Singla
内存与速度直接相关,就像在您的计算机中一样... 当您进行新窗口时,它非常快... 随着您开始安装软件,访问变慢,您的计算机变得更慢。 - Kamal Singla
2
那不是关系,user3471203。 她是谁? 你在说什么? - metamorphosis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接