优化速度:向量队列与指向队列的队列

时间:2015-02-13 15:06:54

标签: c++ vector stl

我正在尝试将向量(实际上是管理向量的对象)存储到队列中,以便稍后处理它们。

这是我目前的实施:

// in constructor:  
q = new boost::lockfree::spsc_queue<MyObject>(num_elements_in_q);
// ...
bool Push(const MyObject& push_me) { return q->push(push_me); }
//  ...  
// in Pop() (i.e., this is how I pop stuff off of the queue)  
MyObject temp;  
q->pop(&temp);  

我想知道存储指针而不是对象是否有意义。以下是新代码的外观:

// in constructor:  
q = new boost::lockfree::spsc_queue<MyObject*>(num_elements_in_q);
// ...
bool Push(const MyObject& push_me) {
  MyObject* ptr = new MyObject(push_me);
  return q->push(push_me);  
}
//  ...  
// in Pop() (i.e., this is how I pop stuff off of the queue)  
MyObject* ptr;  
q->pop(&ptr);
//  do stuff with ptr
delete ptr;

哪种方法最适合最小化推送操作所需的时间?通常最好存储整个MyObject或只存储指针(并动态分配内存)?我意识到通过存储整个MyObject,仍然需要动态内存,因为MyObject中的向量需要调整大小。

我的最终目标是最大限度地缩短推送时间(以及从一个操作到下一个操作的任何时间抖动),代价是内存使用和Pop()执行所需的时间(最高版本需要) Pop()中的一个副本,通过使用指针避免)。

感谢您的帮助。此外,我目前无法访问此系统上的探查器,否则我可能已经得到了答案。

4 个答案:

答案 0 :(得分:3)

如果没有实际测试它,我会说使用new的内存分配可能比复制整个MyObject要花费更多。当然,这取决于MyObject的实现方式。

另一件需要考虑的事情是,假设boost :: lock_free将数据存储在连续内存中,存储对象本身可能会为您提供更高的缓存命中率。因为所有对象都可以通过cpu批量读取,因此可以一起存储在L1缓存中。使用指针将导致CPU从指针指向的内存加载内容,并可能将队列中的其他元素从缓存中踢出。

当然,要100%确定你必须测量它。

答案 1 :(得分:1)

如果速度是最终目标,请考虑使用某种侵入模式。通过侵入,我的意思是,添加到每个对象的链接指针,并使用这些指针来构建您的队列。最大的优点是在向队列添加对象时没有内存分配。如果将所有对象分配到一个大块(例如使用向量),则对象将保持紧密。这意味着迭代列表将不太可能导致缓存未命中。

这意味着您可能需要在队列上实现自己的锁定,但请记住,正确实现的无竞争互斥锁应该或多或少与用于无锁编程的原子操作一样便宜。

请查看:Boost Intrusive,了解模板化提升实施的详细信息。

答案 2 :(得分:1)

鉴于找出正在发生的事情的唯一真正方法是衡量,我用一种粗略的方法来确定我的执行时间(对于两种实现都是如此)。

以下是2500次插入队列的结果。基于函数调用的boost :: timer,时间以秒为单位。请注意,这些是每次通话的平均时间。

用于存放整个物体:
运行1:0.000343423
运行2:0.000338752
运行3:0.000339651
运行4:0.000320011
运行5:0.00034017

用于存储指针:
运行1:0.00033717
运行2:0.00033645
运行3:0.000336106
运行4:0.00033674
运行5:0.000336841

然后我去制作并将测试增加到25,000个插入,因为我想知道最初是否有一些事情发生缓存未命中等。结果如下:

用于存放整个物体:
运行1:0.00023566
运行2:0.000255699
运行3:0.000250765
运行4:0.000239108
运行5:0.000264594

用于存储指针:
运行1:0.000317314
运行2:0.000316985
运行3:0.000414893
运行4:0.000334542
运行5:0.00033179

所以看起来(这只是我的理论)在初始的Push()调用中,对象中找到的向量被正确调整大小。从那里开始,复制构造函数不再需要支付每次调整向量大小的惩罚,它将成为一个更有效的过程。

答案 3 :(得分:-1)

同意在几乎每种情况下,存储指针必须比存储大于指针的东西便宜。

在每种情况下,似乎都有MyObject的复制结构。通过让调用者对对象的生命周期负责,有机会删除这种结构:

  1. 提供rvalue接口将允许使用移动构造,这可能会更轻,这取决于为MyObject选择的表示。
  2. 或者,您可以传递并排队std::unique_ptr<MyObject>智能指针,强制调用者明确管理对象的构造和生命周期保证。
相关问题