久久久久久久av_日韩在线中文_看一级毛片视频_日本精品二区_成人深夜福利视频_武道仙尊动漫在线观看

`std::list<>::sort()` - 為什么突然切換到自

`std::listlt;gt;::sort()` - why the sudden switch to top-down strategy?(`std::listlt;gt;::sort()` - 為什么突然切換到自上而下的策略?)
本文介紹了`std::list<>::sort()` - 為什么突然切換到自上而下的策略?的處理方法,對大家解決問題具有一定的參考價值,需要的朋友們下面隨著小編來一起學習吧!

問題描述

我記得從一開始,實現 std::list<>::sort() 的最流行方法是在 自下而上的時尚(另見是什么讓 gcc std::list 排序實現如此之快?).

我記得有人恰當地將這種策略稱為洋蔥鏈"方法.

至少在 GCC 的 C++ 標準庫實現中是這樣的(例如,請參見 此處).這就是 MSVC 標準庫版本中舊 Dimkumware 的 STL 以及 MSVC 一直到 VS2013 的所有版本中的情況.

然而,VS2015 提供的標準庫突然不再遵循這種排序策略.VS2015 附帶的庫使用了一種相當簡單的自頂向下歸并排序的遞歸實現.這讓我覺得很奇怪,因為自上而下的方法需要訪問列表的中點才能將其分成兩半.由于 std::list<> 不支持隨機訪問,找到中點的唯一方法是逐字迭代列表的一半.此外,一開始就需要知道列表中元素的總數(在 C++11 之前不一定是 O(1) 運算).

盡管如此,VS2015 中的 std::list<>::sort() 正是這樣做的.這是該實現的摘錄,它定位中點并執行遞歸調用

<代碼>...迭代器 _Mid = _STD next(_First, _Size/2);_First = _Sort(_First, _Mid, _Pred, _Size/2);_Mid = _Sort(_Mid, _Last, _Pred, _Size - _Size/2);...

如您所見,他們只是漫不經心地使用 std::next 遍歷列表的前半部分并到達 _Mid 迭代器.

我想知道這個轉換背后的原因是什么?我所看到的只是在每個遞歸級別重復調用 std::next 的效率低下.天真的邏輯說這是.如果他們愿意付出這樣的代價,他們很可能希望得到一些回報.那他們得到什么?我并沒有立即看到這個算法具有更好的緩存行為(與原始的自底向上方法相比).我并沒有立即認為它在預先排序的序列上表現更好.

當然,由于C++11 std::list<> 基本上需要存儲它的元素計數,這使得上面的效率稍微高一些,因為我們總是提前知道元素計數.但這似乎仍然不足以證明在每個遞歸級別上進行順序掃描是合理的.

(誠然,我沒有嘗試在實現之間進行競爭.也許那里有一些驚喜.)

解決方案

請注意,此答案已更新,以解決以下評論中和問題之后提到的所有問題,方法是從列表數組到一個迭代器數組,同時保留了更快的自底向上歸并排序算法,并消除了自頂向下歸并排序算法遞歸導致堆棧溢出的小概率.

我最初沒有考慮迭代器的原因是由于 VS2015 更改為自頂向下,讓我相信嘗試更改現有自底向上算法以使用迭代器存在一些問題,需要切換到較慢的頂部下算法.只有當我嘗試自己分析切換到迭代器時,我才意識到有一個自底向上算法的解決方案.

在@sbi 的評論中,他詢問了自上而下方法的作者 Stephan T. Lavavej,為什么要進行更改.Stephan 的回應是避免內存分配和默認構造分配器".VS2015 引入了不可默認構造和有狀態的分配器,這在使用先前版本的列表數組時會出現問題,因為列表的每個實例分配一個虛擬節點,并且需要進行更改以處理無默認分配器.

Lavavej 的解決方案是改用迭代器來跟蹤原始列表中的運行邊界,而不是內部列表數組.合并邏輯改為使用 3 個迭代器參數,第一個參數是左運行開始的迭代器,第二個參數是左運行結束的迭代器 == 右運行開始的迭代器,第三個參數是右運行結束的迭代器.合并過程使用 std::list::splice 在合并操作期間移動原始列表中的節點.這具有異常安全的額外好處.如果調用者的比較函數拋出異常,列表將被重新排序,但不會發生數據丟失(假設拼接不會失敗).使用之前的方案,如果發生異常,部分(或大部分)數據將在列表的內部數組中,并且數據將從原始列表中丟失.

但是不需要切換到自上而下的歸并排序.最初,我認為 VS2015 切換到自上而下有一些未知的原因,我專注于以與 std::list::splice 相同的方式使用內部接口.后來我決定研究自下而上的切換以使用迭代器數組.我意識到存儲在內部數組中的運行順序是最新的(數組 [0] = 最右邊)到最舊的(數組 [最后] = 最左邊),并且它可以使用與 VS2015 自上而下的方法相同的基于迭代器的合并邏輯.

對于自下而上的歸并排序,array[i] 是一個迭代器,指向具有 2^i 個節點的已排序子列表的開頭,或者它為空(使用 std::list::end 表示為空).每個排序子列表的結尾將是數組中下一個先前非空條目中排序子列表的開頭,或者如果在數組的開頭,則在本地迭代器中(它指向最新的結尾)跑).與自頂向下方法類似,迭代器數組僅用于跟蹤原始鏈表內已排序的運行邊界,而合并過程使用 std::list::splice 移動原始鏈表內的節點.

如果一個鏈表很大,節點分散,就會有很多緩存未命中.自下而上將比自上而下快約 30%(相當于自上而下比自下而上慢約 42%).再說一次,如果有足夠的內存,通常將列表移動到數組或向量,對數組或向量進行排序,然后從排序后的數組或向量創建一個新列表通常會更快.

示例 C++ 代碼:

#define ASZ 32模板 void SortList(std::list<T> &ll){if (ll.size() < 2)//如果無事可做則返回返回;std::list::iterator ai[ASZ];//迭代器數組std::list::iterator mi;//中間迭代器 (end lft, bgn rgt)std::list::iterator ei;//結束迭代器size_t i;for (i = 0; i typename std::list<T>::iterator Merge(std::list<T> &ll,類型名稱 std::list<T>::iterator li,typename std::list<T>::iterator mi,類型名稱 std::list::iterator ei){std::list::iterator ni;(*mi <*li) ?ni = mi : ni = li;而(1){if(*mi <*li){ll.splice(li, ll, mi++);如果(mi == ei)返回 ni;} 別的 {if(++li == mi)返回 ni;}}}


VS2019 的 std::list::sort() 替換代碼示例(合并邏輯被制作成一個單獨的內部函數,因為它現在在兩個地方使用).

私有:模板<class_Pr2>迭代器_Merge(_Pr2 _Pred, 迭代器_First, 迭代器_Mid, 迭代器_Last){迭代器_Newfirst = _First;for (bool _Initial_loop = true;;_Initial_loop = false) {//[_First, _Mid) 和 [_Mid, _Last) 已排序且非空if (_DEBUG_LT_PRED(_Pred, *_Mid, *_First)) {//消耗_Mid如果(_Initial_loop){_Newfirst = _Mid;//更新返回值}splice(_First, *this, _Mid++);如果(_Mid == _Last){返回_Newfirst;//耗盡 [_Mid, _Last);完畢}}else {//消耗 _First++_第一;如果(_First == _Mid){返回_Newfirst;//耗盡 [_First, _Mid);完畢}}}}模板<class_Pr2>void _Sort(迭代器_First,迭代器_Last,_Pr2 _Pred,size_type _Size) {//排序[_First, _Last),使用_Pred,先返回new//_Size 必須是從 _First 到 _Last 的距離如果 (_Size <2) {返回;//沒事做}const size_t _ASZ = 32;//數組大小迭代器_Ai[_ASZ];//要運行的迭代器數組迭代器_Mi;//中間迭代器迭代器_Li;//最后(結束)迭代器尺寸_t_I;//索引到_Aifor (_I = 0; _I <_ASZ; _I++)//空"大批_Ai[_I] = _Last;//_Ai[] == _Last =>空條目//將節點合并到數組中for (_Li = _First; _Li != _Last;) {_Mi = _Li++;對于 (_I = 0; (_I <_ASZ) && _Ai[_I] != _Last; _I++) {_Mi = _Merge(_Pass_fn(_Pred), _Ai[_I], _Mi, _Li);_Ai[_I] = _Last;}如果(_I == _ASZ)_一世 - ;_Ai[_I] = _Mi;}//合并數組運行為單次運行對于 (_I = 0; _I <_ASZ && _Ai[_I] == _Last; _I++);_Mi = _Ai[_I++];而 (1) {對于 (; _I < _ASZ && _Ai[_I] == _Last; _I++);如果(_I == _ASZ)休息;_Mi = _Merge(_Pass_fn(_Pred), _Ai[_I++], _Mi, _Last);}}


這個答案的其余部分是歷史性的.


我能夠根據@IgorTandetnik 的演示重現該問題(舊類型無法編譯,新類型有效):

#include #include <列表>#include <內存>模板 類 MyAlloc : public std::allocator{上市:MyAlloc(T) {}//取消默認構造函數模板MyAlloc(const MyAlloc<U>& other) : std::allocator<T>(other) {}模板struct rebind { typedef MyAlloc;其他;};};int main(){std::list>l(MyAlloc(0));l.push_back(3);l.push_back(0);l.push_back(2);l.push_back(1);l.sort();返回0;}


我早在 2016 年 7 月就注意到了這一變化,并于 2016 年 8 月 1 日通過電子郵件向 P.J. Plauger 發送了有關這一變化的電子郵件.他的回復摘要:

<塊引用>

有趣的是,我們的更改日志并未反映此更改.那可能意味著它是建議的"由我們的一位大客戶和我在代碼審查中得到了.我現在只知道改變已經到來大約在 2015 年秋天.當我查看代碼時,第一個令我印象深刻的是這條線:

 迭代器 _Mid = _STD next(_First, _Size/2);

當然,對于一個大列表,這可能需要很長時間.

代碼看起來比我在 1995 年初寫的更優雅(!),但肯定有更糟糕的時間復雜度.那個版本是建模的在原始 STL 中 Stepanov、Lee 和 Musser 的方法之后.他們在算法選擇上很少被發現是錯誤的.

我現在要恢復到我們最新已知的原始代碼的良好版本.

我不知道 P.J. Plauger 恢復原始代碼是否解決了新的分配器問題,或者 Microsoft 是否或如何與 Dinkumware 交互.

為了比較自頂向下和自底向上方法,我創建了一個包含 400 萬個元素的鏈表,每個元素由一個 64 位無符號整數組成,假設我最終會得到一個幾乎按順序排列的節點的雙向鏈表(即使它們會被動態分配),用隨機數填充它們,然后對它們進行排序.節點不會移動,只是鏈接發生了變化,但現在遍歷列表以隨機順序訪問節點.然后我用另一組隨機數填充那些隨機排序的節點并再次對它們進行排序.我將 2015 年自上而下的方法與之前為匹配 2015 年所做的其他更改而修改的自下而上的方法進行了比較(sort() 現在使用謂詞比較函數調用 sort(),而不是具有兩個單獨的函數).這些是結果.更新 - 我添加了一個基于節點指針的版本,并注意到了從列表中簡單地創建一個向量、排序向量、復制回來的時間.

順序節點:2015版本1.6秒,之前版本1.5秒隨機節點:2015 版本 4.0 秒,先前版本 2.8 秒隨機節點:基于節點指針的版本 2.6 秒隨機節點:從列表創建向量,排序,復制回 1.25 秒

對于順序節點,先前版本只是快一點,但對于隨機節點,先前版本快 30%,節點指針版本快 35%,并從列表中創建一個向量,對向量進行排序,然后復制回來的速度提高了 69%.

下面是 std::list::sort() 的第一個替換代碼,我用來比較之前的自下而上的小數組 (_BinList[]) 方法與 VS2015 的自上而下的方法,我希望比較公平,所以我修改了 < 的副本列表 >.

 void sort(){//排序序列,使用操作符<排序(少<>());}模板無效排序(_Pr2 _Pred){//訂單序列,使用 _Pred如果 (2 > this->_Mysize())返回;const size_t _MAXBINS = 25;_Myt _Templist, _Binlist[_MAXBINS];而(!空()){//_Templist = 下一個元素_Templist._Splice_same(_Templist.begin(), *this, begin(),++開始(),1);//與更大的 bin 數組合并size_t _Bin;for (_Bin = 0; _Bin < _MAXBINS && !_Binlist[_Bin].empty();++_Bin)_Templist.merge(_Binlist[_Bin], _Pred);//不要越過數組的末尾如果(_Bin == _MAXBINS)_斌--;//使用合并列表更新 bin,空 _Templist_Binlist[_Bin].swap(_Templist);}//將 bin 合并回調用者列表對于 (size_t _Bin = 0; _Bin <_MAXBINS; _Bin++)if(!_Binlist[_Bin].empty())this->merge(_Binlist[_Bin], _Pred);}

我做了一些小改動.原始代碼在名為 _Maxbin 的變量中跟蹤實際最大 bin,但最終合并的開銷足夠小,我刪除了與 _Maxbin 關聯的代碼.在數組構建期間,原始代碼的內部循環合并到 _Binlist[] 元素,然后交換到 _Templist,這似乎毫無意義.我將內部循環更改為僅合并到 _Templist,僅在找到空的 _Binlist[] 元素后才進行交換.

下面是我用于另一個比較的 std::list::sort() 的基于節點指針的替換.這消除了與分配相關的問題.如果可能發生比較異常并且發生了比較異常,則必須將數組和臨時列表 (pNode) 中的所有節點追加回原始列表,否則比較異常可能會被視為小于比較.

 void sort(){//排序序列,使用操作符<排序(少<>());}模板無效排序(_Pr2 _Pred){//訂單序列,使用 _Predconst size_t _NUMBINS = 25;_Nodeptr aList[_NUMBINS];//列表數組_Nodeptr pNode;_Nodeptr pNext;_Nodeptr pPrev;if (this->size() <2)//如果無事可做則返回返回;this->_Myhead()->_Prev->_Next = 0;//設置最后一個節點 ->_Next = 0pNode = this->_Myhead()->_Next;//將 ptr 設置為列表的開頭size_t i;for (i = 0; i <_NUMBINS; i++)//零數組aList[i] = 0;while (pNode != 0)//將節點合并到數組中{pNext = pNode->_Next;pNode->_Next = 0;for (i = 0; (i <_NUMBINS) && (aList[i] != 0); i++){pNode = _MergeN(_Pred, aList[i], pNode);aList[i] = 0;}如果(我 == _NUMBINS)一世 - ;aList[i] = pNode;pNode = pNext;}pNode = 0;//將數組合并為一個列表for (i = 0; i <_NUMBINS; i++)pNode = _MergeN(_Pred, aList[i], pNode);this->_Myhead()->_Next = pNode;//更新哨兵節點鏈接pPrev = this->_Myhead();//和 _Prev 指針而 (pNode){pNode->_Prev = pPrev;pPrev = pNode;pNode = pNode->_Next;}pPrev->_Next = this->_Myhead();this->_Myhead()->_Prev = pPrev;}模板_Nodeptr _MergeN(_Pr2 &_Pred, _Nodeptr pSrc1, _Nodeptr pSrc2){_Nodeptr pDst = 0;//目標頭指針_Nodeptr *ppDst = &pDst;//指向頭部或上一個的指針->_下一個如果(pSrc1 == 0)返回 pSrc2;如果(pSrc2 == 0)返回 pSrc1;而 (1){if (_DEBUG_LT_PRED(_Pred, pSrc2->_Myval, pSrc1->_Myval)){*ppDst = pSrc2;pSrc2 = *(ppDst = &pSrc2->_Next);如果(pSrc2 == 0){*ppDst = pSrc1;休息;}}別的{*ppDst = pSrc1;pSrc1 = *(ppDst = &pSrc1->_Next);如果(pSrc1 == 0){*ppDst = pSrc2;休息;}}}返回 pDst;}

I remember that since the beginning of times the most popular approach to implementing std::list<>::sort() was the classic Merge Sort algorithm implemented in bottom-up fashion (see also What makes the gcc std::list sort implementation so fast?).

I remember seeing someone aptly refer to this strategy as "onion chaining" approach.

At least that's the way it is in GCC's implementation of C++ standard library (see, for example, here). And this is how it was in old Dimkumware's STL in MSVC version of standard library, as well as in all versions of MSVC all the way to VS2013.

However, the standard library supplied with VS2015 suddenly no longer follows this sorting strategy. The library shipped with VS2015 uses a rather straightforward recursive implementation of top-down Merge Sort. This strikes me as strange, since top-down approach requires access to the mid-point of the list in order to split it in half. Since std::list<> does not support random access, the only way to find that mid-point is to literally iterate through half of the list. Also, at the very beginning it is necessary to know the total number of elements in the list (which was not necessarily an O(1) operation before C++11).

Nevertheless, std::list<>::sort() in VS2015 does exactly that. Here's an excerpt from that implementation that locates the mid-point and performs recursive calls

...
iterator _Mid = _STD next(_First, _Size / 2);
_First = _Sort(_First, _Mid, _Pred, _Size / 2);
_Mid = _Sort(_Mid, _Last, _Pred, _Size - _Size / 2);
...

As you can see, they just nonchalantly use std::next to walk through the first half of the list and arrive at _Mid iterator.

What could be the reason behind this switch, I wonder? All I see is a seemingly obvious inefficiency of repetitive calls to std::next at each level of recursion. Naive logic says that this is slower. If they are willing to pay this kind of price, they probably expect to get something in return. What are they getting then? I don't immediately see this algorithm as having better cache behavior (compared to the original bottom-up approach). I don't immediately see it as behaving better on pre-sorted sequences.

Granted, since C++11 std::list<> is basically required to store its element count, which makes the above slightly more efficient, since we always know the element count in advance. But that still does not seem to be enough to justify the sequential scan on each level of recursion.

(Admittedly, I haven't tried to race the implementations against each other. Maybe there are some surprises there.)

解決方案

Note this answer has been updated to address all of the issues mentioned in the comments below and after the question, by making the same change from an array of lists to an array of iterators, while retaining the faster bottom up merge sort algorithm, and eliminating the small chance of stack overflow due to recursion with the top down merge sort algorithm.

The reason I didn't originally consider iterators was due to the VS2015 change to top down, leading me to believe there was some issue with trying to change the existing bottom up algorithm to use iterators, requiring a switch to the slower top down algorithm. It was only when I tried to analyze the switch to iterators myself that I realized there was a solution for bottom up algorithm.

In @sbi's comment, he asked the author of the top down approach, Stephan T. Lavavej, why the change was made. Stephan's response was "to avoid memory allocation and default constructing allocators". VS2015 introduced non-default-constructible and stateful allocators, which presents an issue when using the prior version's array of lists, as each instance of a list allocates a dummy node, and a change would be needed to handle no default allocator.

Lavavej's solution was to switch to using iterators to keep track of run boundaries within the original list instead of an internal array of lists. The merge logic was changed to use 3 iterator parameters, 1st parameter is iterator to start of left run, 2nd parameter is iterator to end of left run == iterator to start of right run, 3rd parameter is iterator to end of right run. The merge process uses std::list::splice to move nodes within the original list during merge operations. This has the added benefit of being exception safe. If a caller's compare function throws an exception, the list will be re-ordered, but no loss of data will occur (assuming splice can't fail). With the prior scheme, some (or most) of the data would be in the internal array of lists if an exception occurred, and data would be lost from the original list.

However the switch to top down merge sort was not needed. Initially, thinking there was some unknown to me reason for VS2015 switch to top down, I focused on using the internal interfaces in the same manner as std::list::splice. I later decided to investigate switching bottom up to use an array of iterators. I realized the order of runs stored in the internal array was newest (array[0] = rightmost) to oldest (array[last] = leftmost), and that it could use the same iterator based merge logic as VS2015's top down approach.

For bottom up merge sort, array[i] is an iterator to the start of a sorted sub-list with 2^i nodes, or it is empty (using std::list::end to indicate empty). The end of each sorted sub-list will be the start of a sorted sub-list in the next prior non-empty entry in the array, or if at the start of the array, in a local iterator (it points to end of newest run). Similar to the top down approach, the array of iterators is only used to keep track of sorted run boundaries within the original linked list, while the merge process uses std::list::splice to move nodes within the original linked list.

If a linked list is large and the nodes scattered, there will be a lot of cache misses. Bottom up will be about 30% faster than top down (equivalent to stating top down is about 42% slower than bottom up ). Then again, if there's enough memory, it would usually be faster to move the list to an array or vector, sort the array or vector, then create a new list from the sorted array or vector.

Example C++ code:

#define ASZ 32

template <typename T>
void SortList(std::list<T> &ll)
{
    if (ll.size() < 2)                  // return if nothing to do
        return;
    std::list<T>::iterator ai[ASZ];     // array of iterators
    std::list<T>::iterator mi;          // middle iterator (end lft, bgn rgt)
    std::list<T>::iterator ei;          // end    iterator
    size_t i;
    for (i = 0; i < ASZ; i++)           // "clear" array
        ai[i] = ll.end();
    // merge nodes into array
    for (ei = ll.begin(); ei != ll.end();) {
        mi = ei++;
        for (i = 0; (i < ASZ) && ai[i] != ll.end(); i++) {
            mi = Merge(ll, ai[i], mi, ei);
            ai[i] = ll.end();
        }
        if(i == ASZ)
            i--;
        ai[i] = mi;
    }
    // merge array into single list
    ei = ll.end();                              
    for(i = 0; (i < ASZ) && ai[i] == ei; i++);
    mi = ai[i++];
    while(1){
        for( ; (i < ASZ) && ai[i] == ei; i++);
        if (i == ASZ)
            break;
        mi = Merge(ll, ai[i++], mi, ei);
    }
}

template <typename T>
typename std::list<T>::iterator Merge(std::list<T> &ll,
                             typename std::list<T>::iterator li,
                             typename std::list<T>::iterator mi,
                             typename std::list<T>::iterator ei)
{
    std::list<T>::iterator ni;
    (*mi < *li) ? ni = mi : ni = li;
    while(1){
        if(*mi < *li){
            ll.splice(li, ll, mi++);
            if(mi == ei)
                return ni;
        } else {
            if(++li == mi)
                return ni;
        }
    }
}


Example replacement code for VS2019's std::list::sort() (the merge logic was made into a separate internal function, since it's now used in two places).

private:
    template <class _Pr2>
    iterator _Merge(_Pr2 _Pred, iterator _First, iterator _Mid, iterator _Last){
        iterator _Newfirst = _First;
        for (bool _Initial_loop = true;;
            _Initial_loop       = false) { // [_First, _Mid) and [_Mid, _Last) are sorted and non-empty
            if (_DEBUG_LT_PRED(_Pred, *_Mid, *_First)) { // consume _Mid
                if (_Initial_loop) {
                    _Newfirst = _Mid; // update return value
                }
                splice(_First, *this, _Mid++);
                if (_Mid == _Last) {
                    return _Newfirst; // exhausted [_Mid, _Last); done
                }
            }
            else { // consume _First
                ++_First;
                if (_First == _Mid) {
                    return _Newfirst; // exhausted [_First, _Mid); done
                }
            }
        }
    }

    template <class _Pr2>
    void _Sort(iterator _First, iterator _Last, _Pr2 _Pred,
        size_type _Size) { // order [_First, _Last), using _Pred, return new first
                           // _Size must be distance from _First to _Last
        if (_Size < 2) {
            return;        // nothing to do
        }
        const size_t _ASZ = 32;         // array size
        iterator _Ai[_ASZ];             // array of   iterators to runs
        iterator _Mi;                   // middle     iterator
        iterator _Li;                   // last (end) iterator
        size_t _I;                      // index to _Ai
        for (_I = 0; _I < _ASZ; _I++)   // "empty" array
            _Ai[_I] = _Last;            //   _Ai[] == _Last => empty entry
        // merge nodes into array
        for (_Li = _First; _Li != _Last;) {
            _Mi = _Li++;
            for (_I = 0; (_I < _ASZ) && _Ai[_I] != _Last; _I++) {
                _Mi = _Merge(_Pass_fn(_Pred), _Ai[_I], _Mi, _Li);
                _Ai[_I] = _Last;
            }
            if (_I == _ASZ)
                _I--;
            _Ai[_I] = _Mi;
        }
        // merge array runs into single run
        for (_I = 0; _I < _ASZ && _Ai[_I] == _Last; _I++);
        _Mi = _Ai[_I++];
        while (1) {
            for (; _I < _ASZ && _Ai[_I] == _Last; _I++);
            if (_I == _ASZ)
                break;
            _Mi = _Merge(_Pass_fn(_Pred), _Ai[_I++], _Mi, _Last);
        }
    }


The remainder of this answer is historical.


I was able to reproduce the issue (old sort fails to compile, new one works) based on a demo from @IgorTandetnik:

#include <iostream>
#include <list>
#include <memory>

template <typename T>
class MyAlloc : public std::allocator<T> {
public:
    MyAlloc(T) {}  // suppress default constructor
    
    template <typename U>
    MyAlloc(const MyAlloc<U>& other) : std::allocator<T>(other) {}
    
    template< class U > struct rebind { typedef MyAlloc<U> other; };
};

int main()
{
    std::list<int, MyAlloc<int>> l(MyAlloc<int>(0));
    l.push_back(3);
    l.push_back(0);
    l.push_back(2);
    l.push_back(1);
    l.sort();
    return 0;
}


I noticed this change back in July, 2016 and emailed P.J. Plauger about this change on August 1, 2016. A snippet of his reply:

Interestingly enough, our change log doesn't reflect this change. That probably means it was "suggested" by one of our larger customers and got by me on the code review. All I know now is that the change came in around the autumn of 2015. When I reviewed the code, the first thing that struck me was the line:

    iterator _Mid = _STD next(_First, _Size / 2);

which, of course, can take a very long time for a large list.

The code looks a bit more elegant than what I wrote in early 1995(!), but definitely has worse time complexity. That version was modeled after the approach by Stepanov, Lee, and Musser in the original STL. They are seldom found to be wrong in their choice of algorithms.

I'm now reverting to our latest known good version of the original code.

I don't know if P.J. Plauger's reversion to the original code dealt with the new allocator issue, or if or how Microsoft interacts with Dinkumware.

For a comparison of the top down versus bottom up methods, I created a linked list with 4 million elements, each consisting of one 64 bit unsigned integer, assuming I would end up with a doubly linked list of nearly sequentially ordered nodes (even though they would be dynamically allocated), filled them with random numbers, then sorted them. The nodes don't move, only the linkage is changed, but now traversing the list accesses the nodes in random order. I then filled those randomly ordered nodes with another set of random numbers and sorted them again. I compared the 2015 top down approach with the prior bottom up approach modified to match the other changes made for 2015 (sort() now calls sort() with a predicate compare function, rather than having two separate functions). These are the results. update - I added a node pointer based version and also noted the time for simply creating a vector from list, sorting vector, copy back.

sequential nodes: 2015 version 1.6 seconds, prior version 1.5  seconds
random nodes:     2015 version 4.0 seconds, prior version 2.8  seconds
random nodes:                  node pointer based version 2.6  seconds
random nodes:    create vector from list, sort, copy back 1.25 seconds

For sequential nodes, the prior version is only a bit faster, but for random nodes, the prior version is 30% faster, and the node pointer version 35% faster, and creating a vector from the list, sorting the vector, then copying back is 69% faster.

Below is the first replacement code for std::list::sort() I used to compare the prior bottom up with small array (_BinList[]) method versus VS2015's top down approach I wanted the comparison to be fair, so I modified a copy of < list >.

    void sort()
        {   // order sequence, using operator<
        sort(less<>());
        }

    template<class _Pr2>
        void sort(_Pr2 _Pred)
        {   // order sequence, using _Pred
        if (2 > this->_Mysize())
            return;
        const size_t _MAXBINS = 25;
        _Myt _Templist, _Binlist[_MAXBINS];
        while (!empty())
            {
            // _Templist = next element
            _Templist._Splice_same(_Templist.begin(), *this, begin(),
                ++begin(), 1);
            // merge with array of ever larger bins
            size_t _Bin;
            for (_Bin = 0; _Bin < _MAXBINS && !_Binlist[_Bin].empty();
                ++_Bin)
                _Templist.merge(_Binlist[_Bin], _Pred);
            // don't go past end of array
            if (_Bin == _MAXBINS)
                _Bin--;
            // update bin with merged list, empty _Templist
            _Binlist[_Bin].swap(_Templist);
            }
            // merge bins back into caller's list
            for (size_t _Bin = 0; _Bin < _MAXBINS; _Bin++)
                if(!_Binlist[_Bin].empty())
                    this->merge(_Binlist[_Bin], _Pred);
        }

I made some minor changes. The original code kept track of the actual maximum bin in a variable named _Maxbin, but the overhead in the final merge is small enough that I removed the code associated with _Maxbin. During the array build, the original code's inner loop merged into a _Binlist[] element, followed by a swap into _Templist, which seemed pointless. I changed the inner loop to just merge into _Templist, only swapping once an empty _Binlist[] element is found.

Below is a node pointer based replacement for std::list::sort() I used for yet another comparison. This eliminates allocation related issues. If a compare exception is possible and occurred, all the nodes in the array and temp list (pNode) would have to be appended back to the original list, or possibly a compare exception could be treated as a less than compare.

    void sort()
        {   // order sequence, using operator<
        sort(less<>());
        }

    template<class _Pr2>
        void sort(_Pr2 _Pred)
        {   // order sequence, using _Pred
        const size_t _NUMBINS = 25;
        _Nodeptr aList[_NUMBINS];           // array of lists
        _Nodeptr pNode;
        _Nodeptr pNext;
        _Nodeptr pPrev;
        if (this->size() < 2)               // return if nothing to do
            return;
        this->_Myhead()->_Prev->_Next = 0;  // set last node ->_Next = 0
        pNode = this->_Myhead()->_Next;     // set ptr to start of list
        size_t i;
        for (i = 0; i < _NUMBINS; i++)      // zero array
            aList[i] = 0;
        while (pNode != 0)                  // merge nodes into array
            {
            pNext = pNode->_Next;
            pNode->_Next = 0;
            for (i = 0; (i < _NUMBINS) && (aList[i] != 0); i++)
                {
                pNode = _MergeN(_Pred, aList[i], pNode);
                aList[i] = 0;
                }
            if (i == _NUMBINS)
                i--;
            aList[i] = pNode;
            pNode = pNext;
            }
        pNode = 0;                          // merge array into one list
        for (i = 0; i < _NUMBINS; i++)
            pNode = _MergeN(_Pred, aList[i], pNode);
        this->_Myhead()->_Next = pNode;     // update sentinel node links
        pPrev = this->_Myhead();            //  and _Prev pointers
        while (pNode)
            {
            pNode->_Prev = pPrev;
            pPrev = pNode;
            pNode = pNode->_Next;
            }
        pPrev->_Next = this->_Myhead();
        this->_Myhead()->_Prev = pPrev;
        }

    template<class _Pr2>
        _Nodeptr _MergeN(_Pr2 &_Pred, _Nodeptr pSrc1, _Nodeptr pSrc2)
        {
        _Nodeptr pDst = 0;          // destination head ptr
        _Nodeptr *ppDst = &pDst;    // ptr to head or prev->_Next
        if (pSrc1 == 0)
            return pSrc2;
        if (pSrc2 == 0)
            return pSrc1;
        while (1)
            {
            if (_DEBUG_LT_PRED(_Pred, pSrc2->_Myval, pSrc1->_Myval))
                {
                *ppDst = pSrc2;
                pSrc2 = *(ppDst = &pSrc2->_Next);
                if (pSrc2 == 0)
                    {
                    *ppDst = pSrc1;
                    break;
                    }
                }
            else
                {
                *ppDst = pSrc1;
                pSrc1 = *(ppDst = &pSrc1->_Next);
                if (pSrc1 == 0)
                    {
                    *ppDst = pSrc2;
                    break;
                    }
                }
            }
        return pDst;
        }

這篇關于`std::list&lt;&gt;::sort()` - 為什么突然切換到自上而下的策略?的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網!

【網站聲明】本站部分內容來源于互聯網,旨在幫助大家更快的解決問題,如果有圖片或者內容侵犯了您的權益,請聯系我們刪除處理,感謝您的支持!

相關文檔推薦

Assertion failed (size.widthgt;0 amp;amp; size.heightgt;0)(斷言失敗(size.width0 amp;amp; size.height0))
Rotate an image in C++ without using OpenCV functions(在 C++ 中旋轉圖像而不使用 OpenCV 函數)
OpenCV: process every frame(OpenCV:處理每一幀)
Why can#39;t I open avi video in openCV?(為什么我不能在 openCV 中打開 avi 視頻?)
OpenCV unable to set up SVM Parameters(OpenCV 無法設置 SVM 參數)
Convert a single color with cvtColor(使用 cvtColor 轉換單一顏色)
主站蜘蛛池模板: 欧美一区2区三区3区公司 | 黄色大片在线免费观看 | 狠狠av | 99福利视频 | 日本小电影在线 | 国产乱一区二区三区视频 | 丝袜毛片 | 先锋av资源网 | 特级做a爰片毛片免费看108 | 青青久久 | 久久久成人精品 | 午夜国产 | 久久激情视频 | 91久久久久久 | 免费在线观看黄色av | 亚洲顶级毛片 | 亚洲第一av | 97精品一区二区 | 成人久久久 | 色偷偷噜噜噜亚洲男人 | 99pao成人国产永久免费视频 | 91网站在线观看视频 | 911网站大全在线观看 | 日韩中文一区二区 | 青青草国产在线观看 | 天天看天天爽 | 亚洲激精日韩激精欧美精品 | 国产精品久久一区二区三区 | 范冰冰一级做a爰片久久毛片 | 日产久久 | 波多野结衣一区二区三区 | 久草免费在线视频 | 亚洲视频免费在线观看 | 在线一级片 | 久久久精彩视频 | www.伊人.com | 久久免费精品 | 超碰在线97国产 | 国产精品永久 | 午夜国产精品视频 | 美人の美乳で授乳プレイ |