高效传递std :: vector

时间:2009-11-20 15:37:53

标签: c++ stl vector arguments

当C ++函数接受std::vector参数时,通常的模式是通过const引用传递它,例如:

int sum2(const std::vector<int> &v)
{
   int s = 0;
   for(size_t i = 0; i < v.size(); i++) s += fn(v[i]);
   return s;
}

我相信这个代码会在访问向量元素时导致双重解引用,因为CPU应首先取消引用v以读取指向第一个元素的指针,该指针需要再次取消引用才能读取第一个元素元件。我希望在堆栈上传递矢量对象的浅表副本会更有效。这样的浅拷贝会封装指向第一个元素的指针和大小,指针引用与原始向量相同的内存区域。

int sum2(vector_ref<int> v)
{
   int s = 0;
   for(size_t i = 0; i < v.size(); i++) s += fn(v[i]);
   return s;
}

类似的性能,但通过传递随机访问迭代器对可以实现更少的便利性。 我的问题是:这个想法存在什么缺陷?我希望聪明的人能够接受支付矢量参考的性能成本,或者处理迭代器带来的不便。 p>

修改:根据以下内容,如果我只是将建议的vector_ref类重命名为 slice 范围,请考虑这种情况。目的是使用具有更自然语法的随机访问迭代器对。

6 个答案:

答案 0 :(得分:10)

  

我相信当访问向量元素时,此代码会导致双重解除引用

不一定。编译器很聪明,应该能够eliminate common subexpressions.他们可以看到运算符[]没有改变'指向第一个元素的指针',因此他们不需要让CPU从内存中重新加载它对于每个循环迭代。

答案 1 :(得分:9)

你的想法有什么问题,你已经有两个非常好的解决方案了:

  • 按原样传递矢量,或者按值(编译器通常会删除副本),或者通过(const)引用,并信任编译器以消除双重间接,或者
  • 传递迭代器对。

当然你可以说迭代器对是“不太自然的语法”,但我不同意。对于习惯STL的人来说,这是完全自然的。它非常高效,使用标准算法或您自己的函数为您提供使用范围所需的精确内容。

迭代器对是一种常见的C ++习惯用法,读取代码的C ++程序员可以毫无问题地理解它们,而他们会对你自制的矢量包装器感到惊讶。

如果你真的对性能感到偏执,那就传递一对迭代器。如果语法真的困扰你,传递向量并信任编译器。

答案 2 :(得分:4)

  

这个想法有什么缺陷?

简单:这是过早的优化。替代方案:接受vector<int> const&并使用迭代器或直接将迭代器传递给函数。

答案 3 :(得分:2)

你说这里有一个额外的间接是正确的。如果编译器(在链接时代码生成的帮助下)优化它,那么可以想象(尽管这会令人惊讶)。

你提出的建议有时被称为切片,它在某些情况下被广泛使用。虽然,总的来说,我不确定它是否值得冒险。你必须非常小心无视你的切片(或其他人)。

请注意,如果您使用迭代器进行循环而不是索引,那么您只需要对引用进行几次调用(调用begin()end())而不是n次(以索引为矢量)。

int sum(const vector<int> &v)
{
   int s = 0;
   for (auto it = v.begin(); it != v.end(); ++it) {
       s += fn(*it);
   }
   return s;
}

(我假设优化器会将end()调用提升出来。你可以明确地确定它。)

传递一对迭代器而不是容器本身就像STL习语。这会给你更多的一般性,因为容器的类型可以变化,但所需的解引用数量也是如此。

答案 4 :(得分:1)

按值传递,除非您确定通过引用传递可以提高性能。

当您通过值时,可能会出现复制省略,这将导致类似(如果不是更好)的表现。

戴夫在这里写到:

http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/

答案 5 :(得分:1)

没有双重解除引用,因为编译器可能会将实际指针作为参数传递给向量而不是指向指针的指针。您可以直接尝试这一点,并检查IDE的反汇编视图,了解幕后实际情况:

void Method(std::vector<int> const& vec) {
 int i = vec.back();
}


void SomeOtherMethod() {
  std::vector<int> vec;
  vec.push_back(1);
  Method(vec);
}

这里发生了什么?向量在堆栈上分配。第一次推回被翻译为:

push        eax  // this is the constant one that has been stored in eax
lea         ecx,[ebp-24h] // ecx is the pointer to vec on the stack
call        std::vector<int,std::allocator<int> >::push_back

现在我们调用Method(),传递向量const&amp;:

lea         ecx,[ebp-24h] 
push        ecx  
call        Method (8274DC0h) 

不出所料,指向vector的指针被传递,因为引用只是永久取消引用的指针。现在在Method()中,再次访问向量:

mov         ecx,dword ptr [ebp+8] 
call        std::vector<int,std::allocator<int> >::back (8276100h)

向量指针直接从堆栈中获取并写入ecx。