看板 C_and_CPP 關於我們 聯絡資訊
EdisonX:謝謝您的細心講解,收穫很多,關於 Q6 我要補充一下,實作時 09/17 01:24
EdisonX:我會把它們弄成一份連續的記憶體,也就是malloc一次而非 09/17 01:24
EdisonX:(N+1) 次, 在此情況下做循序讀取差別還會很大嗎? (只剩 09/17 01:26
EdisonX:pointer 多做一次索引動作) 09/17 01:26
舉個實際的例子來說好了,以這個陣列為例: int a[] = {4, 2, 1, 3}; 它在記憶體裡面可能是長這樣的: address 0x100 0x104 0x108 0x10c value 4 2 1 3 我們把它sort以後,就會變成這樣: address 0x100 0x104 0x108 0x10c value 1 2 3 4 接下來我們存取的時候,就是循序的讀下去:0x100 -> 0x104 -> 0x108 -> 0x10c 這樣當然非常好,但是假設data不是int而是欄位很多的物件,一直複製效率就不好 std::vector<int> v{4, 2, 1, 3}; std::sort(v.begin(), v.end()); for (const int x: v) { use(x); // 1 2 3 4 } ------------------------------------------------------------------------------ 你在文中提到的方法是這樣,假設malloc給的空間很爛: address 0x10000 0x00004 0x10004 0xffffc value 4 2 1 3 address 0x00200 0x00204 0x00208 0x0020c value 0x10000 0x00004 0x10004 0xffffc int** arr = 0x00200; 我們sort完成以後,就變成了這樣: address 0x00200 0x00204 0x00208 0x0020c value 0x10004 0x00004 0xffffc 0x10000 於是我們存取的時候就變成了:0x10004 -> 0x00004 -> 0xffffc -> 0x10000, 這樣可視為隨機存取,造成大量cache miss也難以pre-fetch,效能不好, 當然優點是排序的時候我們只有移動pointer而已:) std::vector<std::unique_ptr<int>> v; v.emplace_back(new int(4)); v.emplace_back(new int(2)); v.emplace_back(new int(1)); v.emplace_back(new int(3)); std::sort(boost::make_indirect_iterator(v.begin()), boost::make_indirect_iterator(v.end())); for (const auto& p: v) { use(*p); // 1 2 3 4 } ------------------------------------------------------------------------------ 而你推文所述的方式是這樣的: address 0x100 0x104 0x108 0x10c value 4 2 1 3 address 0x200 0x204 0x208 0x20c value 0x100 0x104 0x108 0x10c int** arr = 0x200; 因為{4, 2, 1, 3}的空間是一起分配連續的,所以不會有上面那種到處亂放的狀況, 而我們sort完了以後,就變成了: address 0x200 0x204 0x208 0x20c value 0x108 0x104 0x10c 0x100 access pattern是:0x108 -> 0x104 -> 0x10c -> 0x100, 一樣排序時我們只需要移動pointer,不用搬動真正的data, 原則上避免了多次malloc所造成的問題,但access pattern還是屬於隨機 std::vector<int> v{4, 2, 1, 3}; std::vector<decltype(v)::iterator> its; for (auto it = v.begin(); it != v.end(); ++it) { its.push_back(it); } std::sort(its.begin(), its.end(), // write your own indirect_less [](decltype(its)::value_type lhs, decltype(its)::value_type rhs) { return *lhs < *rhs; }); for (const auto it: its) { use(*it); // 1 2 3 4 } ------------------------------------------------------------------------------ 至於哪一個效能會最好就要看你的使用情況了,我只能稍微分析一下他們的差異, 另外第二種作法乍看之下好像沒啥用,但它有個特別之處: 它可以是polymorphic objects的容器(hetrogeneous container),例如這樣: struct base { // base can even be an abstract class. virtual void pure_virtual() = 0; }; struct derived : public base { virtual void pure_virtual() override { } }; std::vector<std::unique_ptr<base>> bases; bases.emplace_back(new derived); // ok! -- ※ 發信站: 批踢踢實業坊(ptt.cc) ◆ From: 140.113.252.42
EdisonX:這下真的懂了,謝謝您的細心回覆,感謝 !! :) 09/17 03:04
EdisonX:疑!換個角度講的話,若只要求「歷遍」,而不要求「循序」 09/17 03:06
EdisonX:推文中的碼效能應也不差?唯必須再記一筆起始位址便是.. 09/17 03:07
PkmX:oh...我可能沒有表達清楚 我在兩篇文章中所用的"循序"指的是 09/17 03:19
PkmX:依照記憶體位置"sequential"的讀取 並不是依照資料排序的意思 09/17 03:20
BombCat:現在x86的cache都是讀一個block當單位,堆文中方法應該都 09/17 04:56
BombCat:在cache中,這樣就算是隨機存取還有差嗎? (不是本科請見諒 09/17 04:58
EdisonX:資料量大還是會被擠出cache,因讀取的記憶體順序會跳來跳去 09/17 06:01
BombCat:了解,我想也是這樣 09/17 07:33