C++ 在已知大小时向向量添加元素的基准测试

C++ 在已知大小时向向量添加元素的基准测试,c++,c++11,vector,benchmarking,push-back,C++,C++11,Vector,Benchmarking,Push Back,我已经做了一个很小的基准,用于向向量添加新元素,我知道它的大小 代码: struct foo{ foo() = default; foo(double x, double y, double z) :x(x), y(y), z(y){ } double x; double y; double z; }; void resize_and_index(){ std::vector<foo> bar(1000); for

我已经做了一个很小的基准,用于向向量添加新元素,我知道它的大小

代码:

struct foo{
    foo() = default;
    foo(double x, double y, double z) :x(x), y(y), z(y){

    }
    double x;
    double y;
    double z;
};

void resize_and_index(){
    std::vector<foo> bar(1000);
    for (auto& item : bar){
        item.x = 5;
        item.y = 5;
        item.z = 5;
    }
}

void reserve_and_push(){
    std::vector<foo> bar;
    bar.reserve(1000);
    for (size_t i = 0; i < 1000; i++)
    {
        bar.push_back(foo(5, 5, 5));
    }
}

void reserve_and_push_move(){
    std::vector<foo> bar;
    bar.reserve(1000);
    for (size_t i = 0; i < 1000; i++)
    {
        bar.push_back(std::move(foo(5, 5, 5)));
    }
}

void reserve_and_embalce(){
    std::vector<foo> bar;
    bar.reserve(1000);
    for (size_t i = 0; i < 1000; i++)
    {
        bar.emplace_back(5, 5, 5);
    }
}
resize_and_index: 176 mSec 
reserve_and_push: 560 mSec
reserve_and_push_move: 574 mSec 
reserve_and_embalce: 143 mSec
调用代码:

const size_t repeate = 100000;
auto start_time = clock();
for (size_t i = 0; i < repeate; i++)
{
    resize_and_index();
}
auto stop_time = clock();
std::cout << "resize_and_index: " << (stop_time - start_time) / double(CLOCKS_PER_SEC) * 1000 << " mSec" << std::endl;


start_time = clock();
for (size_t i = 0; i < repeate; i++)
{
    reserve_and_push();
}
stop_time = clock();
std::cout << "reserve_and_push: " << (stop_time - start_time) / double(CLOCKS_PER_SEC) * 1000 << " mSec" << std::endl;


start_time = clock();
for (size_t i = 0; i < repeate; i++)
{
    reserve_and_push_move();
}
stop_time = clock();
std::cout << "reserve_and_push_move: " << (stop_time - start_time) / double(CLOCKS_PER_SEC) * 1000 << " mSec" << std::endl;


start_time = clock();
for (size_t i = 0; i < repeate; i++)
{
    reserve_and_embalce();
}
stop_time = clock();
std::cout << "reserve_and_embalce: " << (stop_time - start_time) / double(CLOCKS_PER_SEC) * 1000 << " mSec" << std::endl;
为什么我会得到这些结果?什么使emplace_back优于 其他人

您得到这些结果是因为您对其进行了基准测试,并且必须得到一些结果:)
在这种情况下,Emplace back做得更好,因为它直接在向量保留的内存位置创建/构造对象。因此,它不必首先在外部创建对象(可能是临时对象),然后将其复制/移动到向量的保留位置,从而节省一些开销

为什么std::move会使性能稍差

如果你问为什么它比emplace更昂贵,那是因为它必须“移动”对象。在这种情况下,移动操作可以很好地简化为复制。因此,一定是复制操作花费了更多的时间,因为此复制不是针对emplace案例进行的。
您可以尝试挖掘生成的汇编代码,看看到底发生了什么。

另外,我不认为将其余函数与“resize_和_index”进行比较是公平的。在其他情况下,对象可能被实例化多次。

首先,reserve_和_push以及reserve_和_push_move在语义上是等价的。您构造的临时foo已经是一个右值(push_-back的右值引用重载已经被使用);在移动中包装它不会改变任何东西,除了可能会模糊编译器的代码,这可以解释轻微的性能损失。(尽管我认为这更可能是噪声)此外,您的类具有相同的复制和移动语义

其次,如果将循环的主体编写为

item = foo(5, 5, 5);
尽管只有分析才能显示这一点。问题是编译器可能会为三个单独的赋值生成次优代码

第三,你也应该试试这个:

std::vector<foo> v(100, foo(5, 5, 5));
向量v(100,foo(5,5,5)); 第四,这个基准测试对编译器非常敏感,因为编译器意识到这些函数实际上都没有做任何事情,只是简单地优化了它们的完整体

现在进行分析。注意,如果您真的想知道发生了什么,您必须检查编译器生成的程序集

第一个版本执行以下操作:

  • 为1000个foo分配空间
  • 循环和默认值分别构造
  • 循环所有元素并重新指定值
  • 这里的主要问题是编译器是否意识到第二步中的构造函数是no-op,并且它可以省略整个循环。组装检查可以证明这一点

    第二和第三个版本执行以下操作:

  • 为1000个foo分配空间
  • 1000次:
  • 构造一个临时的foo对象
  • 确保仍有足够的已分配空间
  • 移动(对于您的类型,相当于一个副本,因为您的类没有特殊的移动语义)临时文件到分配的空间中
  • 增加向量的大小
  • 对于编译器来说,这里有很大的优化空间。如果它将所有操作都内联到同一个函数中,它可能会意识到大小检查是多余的。然后它可能会意识到移动构造函数不能抛出,这意味着整个循环是不可中断的,这意味着它可以将所有增量合并到一个赋值中。如果它没有内联push_-back,它必须将临时文件放在内存中,并传递对它的引用;在这种特殊情况下,有很多方法可以提高效率,但可能性不大

    但是,除非编译器执行其中一些操作,否则我希望这个版本比其他版本慢得多

    第四个版本执行以下操作:

  • 为1000个foo分配足够的空间
  • 1000次:
  • 确保仍有足够的已分配空间
  • 使用带三个参数的构造函数在分配的空间中创建一个新对象
  • 增大尺寸
  • 这与前面的类似,但有两个区别:第一,MS标准库实现push_back的方式,它必须检查传递的引用是否是向量本身的引用;这大大增加了函数的复杂性,抑制了内联。emplace_back没有这个问题。其次,emplace_back获取三个简单的标量参数,而不是对堆栈对象的引用;如果函数没有内联,则传递的效率会显著提高

    除非您专门使用Microsoft的编译器,否则我强烈建议您与其他编译器(及其标准库)进行比较。我还认为我建议的版本会击败你的四个版本,但我还没有介绍这一点


    最后,除非代码真正对性能敏感,否则应该编写可读性最好的版本。(这是我的版本获胜的另一个地方,依我看。)

    我不确定预备队和推、预备队和推的区别是否只是噪音。我使用g++4.8.4做了一个简单的测试,注意到可执行文件大小/附加汇编指令的增加,即使理论上在这种情况下,编译器可以忽略std::move。

    使用哪些编译器和编译器参数执行此测试?哪些编译器参数?尝试设置/O2优化标志。再次编辑它是O2,用于比较:VS2013、Win7、Xeon 1241@3.5 Ghz调整大小和索引:144毫秒保留和推送:199毫秒保留和推送移动:201毫秒保留和保存:111毫秒CGCC 4.8,
    -O3
    :resize_and_index:215.873 mSec reserve_and_push:284.444 mSec reserve_and_push_move:279.426 mSec reserve_and_embalce:252.87 mSec-因此,在机器(运行Lubuntu 14.04的VMVare)、体系结构(一些i3 x64)和编译器中,结果非常复杂
    std::vector<foo> v(100, foo(5, 5, 5));