簡體   English   中英

優化速度:向量隊列與向量指針隊列

[英]Optimizing for Speed: Queue of Vectors vs. Queue of Pointers to Vectors

我正在嘗試將向量(實際上是管理向量的對象)存儲到隊列中,以便稍后進行處理。

這是我當前的實現:

// 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())。

謝謝您的幫助。 另外,我目前無法訪問此系統上的探查器,否則我可能已經有了答案。

如果沒有實際測試,我會說使用new進行內存分配可能比復制整個MyObject花費更多。 當然,這取決於MyObject的實現方式。

要考慮的另一件事是,假設boost :: lock_free將數據存儲在連續內存中,存儲對象本身可能會給您帶來更高的緩存命中率。 因為您的所有對象都可以被cpu批量讀取,因此一起存儲在L1緩存中。 使用指針將導致CPU從指針指向的內存中加載內容,並可能將隊列中的其他元素踢出緩存。

當然,要確保100%必須測量它。

如果速度是最終目標,請考慮以侵入性方式使用某種形式。 侵入式,我的意思是,向每個對象添加鏈接指針,並使用這些指針來構造隊列。 最大的優點是將對象添加到隊列時內存分配為零。 如果將所有對象分配在一個大塊中(例如使用矢量),則對象將保持緊密靠近。 這意味着遍歷該列表將不太可能引起高速緩存未命中。

這確實意味着您可能需要在隊列上實現自己的鎖定,但是請記住,正確實現的無競爭的互斥鎖應與用於無鎖編程的原子操作差不多便宜。

看一下: Boost Intrusive ,了解模板化boost實現的詳細信息。

鑒於弄清正在發生的事情的唯一真實方法是測量,因此我使用了一種粗略的方法來弄清楚我的執行時間(對於兩個實現都是如此)。

以下是隊列中插入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()調用中,正確調整了對象中找到的向量的大小。 從那里開始,復制構造函數不再需要每次都為向量調整大小而付出代價,它變得更加高效。

同意在幾乎每種情況下,存儲指針都必須比存儲比指針大的東西便宜。

在每種情況下,似乎都有MyObject的副本構造。 通過讓調用者負責對象的生存期,就有機會刪除此構造:

  1. 提供右值接口將允許改用move結構,這可能要輕得多,具體取決於為MyObject選擇的表示形式。
  2. 或者,您可以傳遞std::unique_ptr<MyObject>智能指針並使之排隊,從而強制調用者顯式管理對象的構造和生存期保證。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM