std::vector 的高效传递

发布于 2024-08-11 09:57:53 字数 777 浏览 2 评论 0原文

当 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;
}

通过传递随机访问迭代器对可以实现类似的性能,但便利性要差得多。 我的问题是:这个想法有什么缺陷?我希望聪明人应该有一些充分的理由接受支付矢量引用的性能成本,或者处理迭代器的不便。

编辑:根据下面的评论,如果我只是将建议的 vector_ref 类重命名为 slicerange,请考虑这种情况。目的是使用具有更自然语法的随机访问迭代器对。

When a C++ function accepts an std::vector argument, the usual pattern is to pass it by const reference, such as:

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;
}

I believe that this code results in double dereferencing when the vector elements are accessed, because the CPU should first dereference v to read the pointer to the first element, which pointer needs to be dereferenced again to read the first element. I would expect that it would be more efficient to pass a shallow copy of the vector object on the stack. Such shallow copy would encapsulate a pointer to the first element, and the size, with the pointer referencing the same memory area as the original vector does.

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

Similar performance, but much less convenience could be achieved by passing a random access iterator pair. My question is: what is flawed with this idea? I expect that there should be some good reason that smart people accept to pay the performace cost of vector reference, or deal with the inconvenience of iterators.

Edit: Based on the coments below, please consider the situation if I simply rename the suggested vector_ref class to slice or range. The intention is to use random-access iterator pairs with more natural syntax.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

哽咽笑 2024-08-18 09:57:53

我认为访问向量元素时此代码会导致双重解引用

但不一定。编译器非常聪明,应该能够消除常见的子表达式。他们可以看到运算符[] 不会更改“指向第一个元素的指针”,因此不需要让 CPU 在每次循环迭代时从内存中重新加载它。

I believe that this code results in double dereferencing when the vector elements are accessed

Not necessarily. Compilers are pretty smart and should be able to eliminate common subexpressions. They can see that the operator [] doesn't change the 'pointer to the first element', so they have no need make the CPU reload it from memory for every loop iteration.

风月客 2024-08-18 09:57:53

你的想法的问题在于你已经有了两个完美的解决方案:

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

当然,你可以说迭代器对是“不太自然的语法”,但我不同意。对于任何习惯了 STL 的人来说,这都是非常自然的。它非常高效,并且可以使用标准算法或您自己的函数为您提供处理该范围所需的信息。

迭代器对是常见的 C++ 习惯用法,阅读您的代码的 C++ 程序员将毫无问题地理解它们,而他们会对您自制的向量包装器感到惊讶。

如果您对性能确实很偏执,请传递这对迭代器。如果语法确实让您烦恼,请传递向量并信任编译器。

What's wrong with your idea is that you already have two perfectly good solutions:

  • Pass the vector as is, either by value (where the compiler will often eliminate the copy), or by (const) reference, and trust the compiler to eliminate the double indirection, or
  • Pass an iterator pair.

Of course you can argue that the iterator pair is "less natural syntax", but I disagree. It is perfectly natural to anyone who's used to the STL. It is efficient, and gives you exactly what you need to work with the range, using std algorithms or your own functions.

Iterator pairs are a common C++ idiom, and a C++ programmer reading your code will understand them without a problem, whereas they're going to be surprised at your home-brewed vector wrappers.

If you're really paranoid about performance, pass the pair of iterators. If the syntax really bothers you, pass the vector and trust the compiler.

忱杏 2024-08-18 09:57:53

这个想法有什么缺陷?

很简单:这是不成熟的优化。替代方案:接受 vector。 const& 并使用迭代器或将迭代器直接传递给函数。

What is flawed with this idea?

Simple: It's premature optimization. Alternatives: Accept a vector<int> const& and use iterators or pass iterators directly to the function.

莳間冲淡了誓言ζ 2024-08-18 09:57:53

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

您提出的有时称为切片,并且在某些情况下广泛使用。不过,总的来说,我不确定是否值得冒这样的危险。您必须非常小心地避免使您的切片(或其他人的切片)无效。

请注意,如果您在循环中使用迭代器而不是索引,那么您只需解引用引用几次(调用 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成语。这将为您提供更多的通用性,因为容器的类型可能会有所不同,但所需的取消引用的数量也可能会有所不同。

You're right that there's an extra indirection here. It's conceivable (though it would be surprising) if the compiler (with the help of link-time code generation) optimized it away.

What you've proposed is sometimes called slicing, and it's used extensively in some situations. Though, in general, I'm not sure it's worth the dangers. You have to be very careful about invaliding your slice (or someone else's).

Note that if you used iterators for the loop instead of indexing, then you'd deref the reference only a couple times (to call begin() and end()) rather than n times (to index into the vector).

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

(I'm assuming the optimizer will hoist the end() calls out of the loop. You could do it explicitly to be certain.)

Passing a pair of iterators instead of the container itself seems like the STL idiom. That would give you more generality, as the type of container can vary, but so can the number of dereferences needed.

晨光如昨 2024-08-18 09:57:53

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

当您按值传递时,可能会发生复制省略,这将导致类似的性能(如果不是更好的话)。

戴夫在这里写了相关内容:

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

Pass by value unless you're certain passing by reference improves performances.

When you pass by value, copy elision may occur which will result in similar if not better performances.

Dave wrote about it here:

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

兮子 2024-08-18 09:57:53

不存在双重解引用,因为编译器可能会将指向向量的实际指针作为参数传递,而不是指向指针的指针。您可以简单地尝试一下,并检查 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&:

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

毫不奇怪,指向向量的指针被传递,因为引用只不过是永久取消引用的指针。现在在 Method() 内部,再次访问向量:

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

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

There is no double dereferencing because the compiler will probably pass the real pointer to the vector as the argument and not a pointer to a pointer. You can simply try this out and check the disassembly view of your IDE for what is actually going on behind the scenes:

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


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

What happens here? The vector is allocated on the stack. The first push back is translated to:

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

Now we call Method(), passing the vector const&:

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

Unsurprisingly, the pointer to the vector is passed as references are nothing but permanently dereferenced pointers. Now inside Method(), the vector is accessed again:

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

The vector pointer is taken directly from the stack and written to ecx.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文