std :: push_back使用起来相对昂贵吗?

时间:2019-05-30 00:44:11

标签: c++ performance vector

我想提高以下代码的性能。哪些方面可能会影响代码在执行时的性能?

此外,考虑到可以向容器中添加多少个对象没有限制,可以对“ Object”或“ addToContainer”进行哪些改进以提高程序的性能?

我想知道C ++中的std :: push_back是否以任何方式影响代码的性能?尤其是添加到列表没有限制时。

struct Object{
    string name;
    string description;
};

vector<Object> container;
void addToContainer(Object object) {
    container.push_back(object);
}

int main() {
    addToContainer({ "Fira", "+5 ATTACK" });
    addToContainer({ "Potion", "+10 HP" });
}

3 个答案:

答案 0 :(得分:2)

push_back可能会非常昂贵,但与其他所有内容一样,它取决于上下文。以这个可怕的代码为例:

std::vector<float> slow_func(const float* ptr)
{
   std::vector<float> v;
   for(size_t i = 0; i < 256; ++i)
     v.push_back(ptr[i]);
   return v;
}

每次调用push_back都必须执行以下操作:

  1. 检查向量中是否有足够的空间
  2. 如果没有,请分配新内存,然后将旧值复制到新向量中
  3. 将新项目复制到向量的结尾
  4. 增量结束

现在,这里的性能存在两个大问题。首先,每个push_back操作都依赖于先前的操作(因为先前的操作已修改结束,并且如果必须调整大小,则可能取决于数组的整个内容)。这几乎破坏了代码中的任何矢量化可能性。在这里看看:

https://godbolt.org/z/RU2tM0

使用push_back的func不能构成非常漂亮的asm。它实际上被迫一次被迫复制单个浮动。现在,如果您将其与另一种方法进行比较,在该方法中您首先调整大小,然后分配;编译器只是用对new的调用和对memcpy的调用来替换全部代码。这将比以前的方法快几个数量级。

std::vector<float> fast_func(const float* ptr)
{
   std::vector<float> v(256);
   for(size_t i = 0; i < 256; ++i)
     v[i] = ptr[i];
   return v;
}

但是,它很大,但是push_back的相对性能在很大程度上取决于数组中的项目是否可以被简单地复制(或移动)。如果您举个例子,您会做一些愚蠢的事情:

struct Vec3 {
   float x = 0;
   float y = 0;
   float z = 0;
};

现在,当我们这样做时:

std::vector<Vec3> v(256);

编译器将分配内存,但也将被迫将所有值设置为零(如果您要再次覆盖它们,这将毫无意义!)。解决此问题的明显方法是使用其他构造函数:

std::vector<Vec3> v(ptr, ptr + 256);

因此,实际上,仅在以下情况之一时才使用push_back(嗯,实际上,在大多数情况下,您应该更喜欢emplace_back):

  1. 其他元素偶尔会添加到向量中
  2. 或者,要添加的对象构造起来很复杂(在这种情况下,请使用emplace_back!)

答案 1 :(得分:2)

在执行任何操作之前,请对代码进行概要分析并获得基准。进行更改配置文件后,代码并获得基准。比较基准。如果不这样做,那就是掷骰子。它更快吗?谁知道。

个人资料个人资料个人资料。

使用push_back时,您有两个主要问题:

  1. 填充vector时调整其大小,并且
  2. 将对象复制到vector中。

根据添加项目的方式,您可以对push_back的调整大小成本进行许多改进。

例如,策略性地使用reserve以最小化调整大小。如果您知道要添加多少个项目,可以检查capacitysize来确定是否值得花时间reserve来避免多次调整大小。请注意,这需要了解vector的扩展策略,并且特定于实现。对一个vector实现的优化可能是另一个实现上的严重错误。

您可以使用insert一次添加多个项目。当然,如果您需要在代码中添加另一个容器以批量插入,这几乎是无用的。

如果您不知道要传入多少个项目,不妨让vector进行其工作并优化添加项目的方式。

例如

void addToContainer(Object object) // pass by value. Possible copy 
{
    container.push_back(object); // copy
}

这些副本可能很昂贵。摆脱它们。

void addToContainer(Object && object) //no copy and can still handle temporaries
{
    container.push_back(std::move(object)); // moves rather than copies 
}

std::string通常很便宜。

addToContainer的变体可以与

一起使用
addToContainer({ "Fira", "+5 ATTACK" });
addToContainer({ "Potion", "+10 HP" });

,并且可能只迁移一个指针和每个string少的簿记变量。他们是临时工,所以没有人会在乎是否会撕破他们的胆量并扔掉尸体。

对于现有的Object

Object o{"Pizza pop", "+5 food"};
addToContainer(std::move(o));

如果它们是消耗性的,它们也会被移动。如果它们不是消耗品...

void addToContainer(const Object & object) // no copy
{
    container.push_back(object); // copy
}

您的重载很难做到。

把这个扔出去

如果您已经有很多项目要知道在列表中,而不是一次全部添加,请使用初始化列表:

vector<Object> container{
    {"Vorpal Cheese Grater", "Many little pieces"},
    {"Holy Hand Grenade", "OMG Damage"}
};

答案 2 :(得分:0)

没有任何其他要求,很遗憾,这是最有效的:

 void addToContainer(Object) { } 

回答其余的问题。通常,push_back只会添加到分配的向量O(1)的末尾,但有时需要增加向量,可以将其摊销,但为O(N)

同样,不使用string而是保留char *可能会更有效率,尽管除非总是添加文字,否则内存管理可能很棘手