效能特性測試系列1——STL容器,QT容器效能相關比較和總結
閒話就不多扯了,本次測試了qt容器,和stl容器相關的效率,增加自己的理解,畢竟耳聽為虛,眼見為實,書和資料怎麼說都只是一個理論,直接測試效能才是王道。
流程
qt,stl容器對應關係對比->橫向比較每個對應關係容器效率->縱向比較各自容器效率->總結表格。
測試環境:Qt5.7,vs2015。
序、qt,stl容器對應關係對比圖
下面這張圖片是轉載的,正好看到了。該圖將qt、stl的順序容器,和關聯容器的對應關係,分別羅列出來了,一目瞭然,在此引用下。但是缺少容器介面卡,所以我在下方自己做了個表,加入的容器介面卡。
本圖片轉載自 點選跳轉
從上圖能夠清楚看到一一對應的關係,所以就不多解釋了,下方是自己做的一個表格,主要是補充容器介面卡對應關係:
本表格參考來源(cppAPI官網):點選跳轉
從程式碼裡我們也能清楚的看到stl的容器介面卡的原型:
至此,基本的容器對應關係已經明瞭,開始比較各容器間的效能。
順序容器
一、 std::vector與QVector:
理論:
vector的內部儲存結構為線性表。在建立一個vector 後,它會自動在記憶體中分配一塊連續的記憶體空間進行資料儲存,當動態新增的資料超過vector 預設分配的大小時要進行記憶體的重新分配、拷貝與釋放,所以對於隨機訪問的速度很快(按下標),但是對於插入尤其是在頭部,中間插入元素速度很慢,在尾部插入速度很快。 並且要vector 達到最優的效能,最好在建立vector 時就指定其空間大小。
特點:隨機訪問快,插入慢,查詢快,尾部增加快。
對比理論:
這兩者效率理論上是差不多的,且可以相互轉換。QVector的insert是用下標索引,std::vector的insert是用迭代器索引。但實測中QVector一樣有迭代器索引方式,並且用這兩種方式效率上沒有區別。
各自測試:
1、 std::vector: 插入和刪除中,從頭部和中間insert插入耗時是從尾部插入的十倍,而pushback比insert要高效率很多,erase同樣要比pop方式慢上千倍不止,所以除非特殊情況,一般用push和pop。
2、 QVector:同上。
在release版本下,QVector和std::vector在尾部位置insert插入,查詢上,較之std::vector稍好一點, push上沒有太大區別,但是erase,popback刪除上要比std::vector慢上一些;
總結:
對於vector,選擇push和pop來增刪都是讓效率更高的方式,而在選擇QVector和std::vector上沒有太大的區別,但考慮到pop耗時,一般情況下std::vector更好。
QVector≈std::vector
二、 std::List與QLinkedList與 QList:
理論:
1、std::list和QlinkedList內部結構為雙向連結串列(每一個節點都包括一個資訊塊(即實際儲存的資料)、一個前驅指標和一個後驅指標。)即記憶體區域不是連續的,元素也是在堆中存放,通過指標來進行資料的訪問,因而隨機訪問的速度很慢(不支援下標,vector是直接找到元素地址,而它需要一個個比較),但是可以迅速地在任何節點進行插入和刪除操作。因為list 的每個節點儲存著它在連結串列中的位置,插入或刪除一個元素僅對最多三個元素有所影響(而vector會對操作點之後的所有元素的儲存地址都有所影響)。
2、QList其實不是連結串列,是優化過的vector,官方的形容是array list。它的儲存方式是分配連續的node,每個node的資料成員不大於一個指標大小,所以對於int、char等基礎型別,它是直接儲存(所以按照記憶體對齊的話,應該會記憶體耗損更大),對於Class、Struct等型別,它是儲存物件指標,有點類似std::deque。QList的實現模式,優點主要在於快速插入。因為其元素大小不會超過sizeof(void*),所以插入時只需要移動指標,而非如vector那樣移動物件。並且,由於QList儲存的是void*,所以可以簡單粗暴的直接用realloc()來擴容。QList的增長策略是雙向增長,所以對prepend支援比vector好得多,使用靈活性高於Vector和LinkedList。缺點是每次存入物件時,需要構造Node物件,帶來額外的堆開銷。
特點:插入刪除快,隨機訪問慢,查詢慢,可頭尾push,pop。
測試:
Std::List: insert方式,從中間插入和從尾部插入,與push的兩種方式,相差不大,但是從頭部插入會稍微慢。
QLinkedList: 同上。
QList: insert方式,從頭部插入和從尾部插入,與push兩種方式相差不大,但是從中間插入相差巨大(慢很多)。,erase和兩種pop方式也沒差別。查詢方式上支援下標訪問。
總體上來說,刪除和插入,QLinkedList於std::list基本上每種的操作效能都差不多,std:: list中間插入稍慢,而QList除了中間insert,比兩者都要好,也和他結構相符合。
縱向比較:std::List在insert,erase上要比std::vector快上許多,pushpop上更慢,查詢上也更慢。
結論:
從各方面來說,選擇QLinkedList相較於std::list其實無所謂,沒什麼區別。但是出去中間插入,對比QList,都要差上不少,也是官方最推薦的容器,原文是這麼說的——“在大多數情況下,都推薦使用QList,它提供更加快速的插入,並且(編譯後)可以展開為更少的程式碼量。”
三、 std::deque:
理論:
是一種優化了的、對序列兩端元素進行新增和刪除操作的基本序列容器。允許較為快速地隨機訪問,但不像vector 把所有的物件儲存在一塊連續的記憶體塊,而是由一段一段的定量連續空間構成,並且在一個對映結構中儲存對這些塊及其順序的跟蹤。
理論上來說,向deque 兩端新增或刪除元素的開銷很小。它不需要重新分配空間,所以向末端增加元素比vector 更有效(測試沒更快)。實際上,deque 是對vector 和list 優缺點的結合,它是處於兩者之間的一種容器。
特點:方位隨機,但較vector慢。內部插入刪除不及list,可以兩端插入刪除。
測試結果:
速度比較:
Insert(中間插入時,deque耗時大兩者很多): std::list>std::deque>std::vector;
Push(vector沒前插入): std:;vector> std::deque >std::list;
Erase: std::list>std::deque>std::vector;
Pop(vector沒有前pop):std::vector≈std::deque>std::list
Find: std::vector>std::list>>std::deque
當然,和QList比,sed::deque完敗。
總結:基本上,除去查詢,deque就是介於std::list和std::vector間的平衡體,沒有嚴重的短板,也能有不錯的效率。
關聯容器
一、 Std::set 與 QSet,std:: unordered_set
理論(基於stl):
set本質是二叉樹。所以插入只需移動指標,指向新節點就行,set使用二分法查詢,所以時間複雜度為log2n,而std::set相當於有一個固定“存貯位置”的,也就是說在其它元素沒有變化的情況下,把位於begin位置的元素取出來(erase),再放回去(insert),還是會處於begin的位置的。Std::set(紅黑樹)。QSet與std::unordered_set對應,兩者皆是無序的。不允許重複值。
理論上來說:
在插入操作和刪除操作上比vector快,但查詢或新增末尾的元素時會有些慢。
特點:唯一,std::set有序,插入刪除快。
測試:
QSet不提供從位置插入,只能預設方式插入。當然,指定位置插入也沒有什麼卵用。
1、std::set與std::unordered_set: unordered_set在查詢方面上有很大優勢(unordered_set是通過雜湊來查詢的),其餘兩者相差不大。
2、QSet與std::unordered_set:預設插入方式上Qset快,find要std::unordered_set稍快,其餘相差不大。
結論:
在需要有序情況下選std::set更合適;考慮查詢效能std::unordered_set更好,Qset優勢不大,且只能預設插入。
二、std::map與QMap:
理論:
二叉樹。key-value對應關係的一對一的資料儲存能力(pair結構)。key不可重複,且按一定順序排列(其實我們可以將set 也看成是一種key-value關係的儲存,只是它只有鍵沒有值。它是map 的一種特殊形式)。
特點: key-value對應,有序,插入刪除快。
對比結果:
總體上來說,兩者幾乎沒有任何效能上的差異。
縱向比較:與std::set幾乎沒有效能差異。
結論:總體上來說,QMap和std::map幾乎沒有任何效能上的差異。
三、std:: unordered_map與QHash:
理論:
1、 C++11的新特性,原本是boost的。基本上unordered_map和map類似,都是儲存的key-value的值,可以通過key快速索引到value。但不同的是unordered_map不會根據key的大小進行排序,儲存時是根據key的hash值判斷元素是否相同,即unordered_map內部元素是無序的,而map中的元素是按照二叉搜尋樹儲存,進行中序遍歷會得到有序遍歷。
2、 QHash —— std::unordered_map都是各自實現了自己的hashTable,然後查詢上都是用node->next的方式逐一對比,不支援互轉。效能上更多的應該是看hash演算法。QHash為常用的qt資料型別都提供好了qHash()函式,使用者自定型別也能通過自己實現qHash()來存入QHash容器。
特點:無序,key-value,插入刪除快。
測試:
總體上來說,Insert方式QHash要更快,但是find上std::unordered_map遠勝QHash。
縱向比較:與std::map相比,查詢要比std::map效率高很多,別的幾乎沒有效能差異(std::的查詢效率≈QHash)。
總結:考慮效能上的優勢,一般情況下,用std::unordered_map更好。
四、std::multimap與QMultiMap:
理論:
multimap的原理和map基本上是相似的,它允許“key”在容器中可以不唯一。
特點:key-value對應,有序,插入刪除快。
測試:
橫向上: std::multimap與QMultiMap在效能上幾乎沒有區別。
縱向上:與std::map相當,也就是說find方面比unordered_map 差。
總結:
在std::multimap與QMultiMap這兩者的選擇上,沒有太大的區別,效能上也大致一樣。
容器介面卡
一、std:: stack與QStack:
理論:
1、棧是一種容器介面卡,特別為後入先出而設計的一種(LIFO ),那種資料被插入,然後再容器末端取出。
棧實現了容器介面卡,這是用了一個封裝了的類作為他的特定容器,提供了一組成員函式去訪問他的元素,元素從特定的容器,也就是堆疊的頭取出袁術。
棧stack 的特點是後進先出,所以它關聯的基本容器可以是任意一種順序容器,因為這些容器型別結構都可以提供棧的操作有求,它們都提供了push_back 、pop_back 和back 操作。也因此,標準的容器類模板vector, deque 和list可以使用,預設情況下,如果沒有容器類被指定成為一個提別的stack 類,標準的容器類模板就是deque 佇列。
2、而QStack是繼承自QVector的,QVector的函式基本都支援。
特點:後進先出
測試:
橫向:結果上來看,插入和刪除效率QStack要高。
縱向: QStack在插入上較QVector有優勢,其他幾乎沒差異。
總結:只從效能上來說,QStack要高,支援函式也多。
二、std::Queue與QQueue:
理論:
1、Std:;queue單向佇列與棧有點類似,一個是在同一端存取資料,另一個是在一端存入資料,另一端取出資料。單向佇列中的資料是先進先出(First In First Out,FIFO)。
佇列queue 的特點是先進先出,介面卡要求其關聯的基礎容器必須提供pop_front 操作,因此其不能建立在vector 容器上。
2、而QQueue繼承自QList,QList的函式也基本都支援。
特點:先進先出。
測試:
橫向:插入和刪除std::Queue稍微好一點。
縱向: std::queue與std::stack效能幾乎無差異,QQueue較QStack快,與父類QList比較幾乎無差異。
總結:適用於有需求的。
三、std:: priority_queue:
理論:
優先順序佇列priority_queue 介面卡要求提供隨機訪問功能,因此不能建立在list 容器上。基於vector 容器實現。STL裡面預設用的是 vector. 比較方式預設用 operator< , 所以如果後面倆個引數預設的話,優先佇列就是大頂堆,隊頭元素最大。
特點:優先順序佇列(優先順序高的元素先出佇列)。
測試:
縱向:速度於前兩個更小。
總結:適用於有需求的
總結表格:
一、Qt縱向:
二、STL縱向:
三、QT與STL:
四、一句話:
基本上看需求選擇,QT容器和STL容器差別不是很大,細節的對照表格可以看出來;
總體上來說,map,set,mutlimap這些關聯容器,效能差別不大,而無序的關聯容器,也只是查詢上有優勢。
最後,QList表現很搶眼。