作者:诸葛不亮
链接:https://www.zhihu.com/question/50156390/answer/119663575
来源:知乎
著作权归作者所有,转载请联系作者获得授权。
链接:https://www.zhihu.com/question/50156390/answer/119663575
来源:知乎
著作权归作者所有,转载请联系作者获得授权。
Qt的容器类具体分析可见官方文档:
http://doc.qt.io/qt-5.7/containers.html
里面有关于时间复杂度、迭代器等各方面的概述和表格对比
各个容器的具体实现,可以看代码,或者看容器类文档开头的详细介绍
QTL比起STL的话,最大的特点是统一用了写时复制技术。缺点是不支持用户自定allocator
简单类比下吧,待会儿我再写个benchmark测一测
另外,QList的增长策略是双向增长,所以对prepend支持比vector好得多,使用灵活性高于Vector和LinkedList。缺点是每次存入对象时,需要构造Node对象,带来额外的堆开销。
然而Qt对QList的支持还是很充足,用户甚至可以用宏为自己要放入list的对象进行属性指定(POD?Class但可以直接memcpy?只能用拷贝构造?)来辅助编译优化。
对了,QList虽然是个特殊的Vector,但提供的接口仍然是和std::list的互转,挺奇葩的……
唯一的特例是 QWidget类型及其子类,这些类型 绝对不允许直接保存,只能用指针保存,哪怕用QTL也是这样。
但是, 不推荐用STL保存Qt对象,因为 代码风格不一致。不过QTL同时提供了Qt style API和STL style API,如果有和第三方库混合编程的需求,推荐用STL style API,这样可以随时替换类型。另外,QTL还提供了Java style iterator,对于一些习惯Java语法的用户会很方便。
对了,QTL比起STL,性能差别不明显,主要差异在
上图是Qt Document里Container Classes章节对各容器类性能的统计。从这里也可以看出,QList其实是个特殊的QVector,QSet其实是个特殊的QHash
============================
Benchmark算法设计
数据元素均为int,无论key还是value(懒得构造随机string了)
吐槽
===============================
QList、QVecotr、std::vector存储复杂类型测试
结果如图。QList性能略小于等于QVector,二者性能和std::vector无本质差别。 结果如图。QList性能略小于等于QVector,二者性能和std::vector无本质差别。
总结:QList在提供最大化的易用性的同时,带来了最小的性能损耗,若不使用prepend的话,可以换用QVecotr。
以下摘自QList帮助文档:
Benchmark代码
还尝试了取消中间变量,添加时使用push_back(PODStruct()),读取时使用const PODStruct& data,性能会有明显提升,但三者的性能对比不变。
里面有关于时间复杂度、迭代器等各方面的概述和表格对比
各个容器的具体实现,可以看代码,或者看容器类文档开头的详细介绍
QTL比起STL的话,最大的特点是统一用了写时复制技术。缺点是不支持用户自定allocator
简单类比下吧,待会儿我再写个benchmark测一测
- QLinkedList —— std::list 两者都是双向链表,两者可以直接互转
- QVector —— std::vector 两者都是动态数组,都是根据sizeof(T)进行连续分配,保证成员内存连续,能够用data()直接取出指针作为c数组使用,两者可以直接互转
- QMap —— std::map 两者都是红黑树算法,但不能互转,因为数据成员实现方式不同。std::map的数据成员用的是std::pair,而QMap用的是自己封装的Node,当然还是键值对
- QMultiMap —— std::multimap 同上
- QList —— stl没有对应类。QList其实不是链表,是优化过的vector,官方的形容是array list,据说它更类似于boost::ptr_deque,不过我没用过后者。它的存储方式是分配连续的node,每个node的数据成员不大于一个指针大小,所以对于int、char等基础类型,它是直接存储,对于Class、Struct等类型,它是存储对象指针。
另外,QList的增长策略是双向增长,所以对prepend支持比vector好得多,使用灵活性高于Vector和LinkedList。缺点是每次存入对象时,需要构造Node对象,带来额外的堆开销。
然而Qt对QList的支持还是很充足,用户甚至可以用宏为自己要放入list的对象进行属性指定(POD?Class但可以直接memcpy?只能用拷贝构造?)来辅助编译优化。
对了,QList虽然是个特殊的Vector,但提供的接口仍然是和std::list的互转,挺奇葩的……
- QBitArray —— std::bitset 功能相同,实现相似,都是构造一个array,用位操作来存取数据。不同的是,QBitArray数据的基础元素是unsigned char,而bitset是unsigned long。所以QBitArray可能在空间消耗上会省一点。至于效率上么……得让懂编译的人来解答了,我是觉得,32位cpu上,char的位操作和int的位操作应该是一样的开销。
- QHash —— std::hash_map 都是各自实现了自己的hashTable,然后查询上都是用node->next的方式逐一对比,不支持互转,性能上更多的应该是看hash算法。QHash为常用的qt数据类型都提供好了qHash()函数,用户自定类型也能通过自己实现qHash()来存入QHash容器。
- QSet —— std::set 二者不能互转,实现方式有本质的区别。QSet其实是改造过的QHash,用key来存数据,value置空。而std::set用的是红黑树。所以,效率上一般应该是QSet高,但数据大了可能会有撞车问题
- QVarLengthArray —— std::array std::array是用class封装后的定长数组,数据分配在栈上。QVarLengthArray类似,默认也是定长数组,栈分配。但用户依旧可以添加超出大小的内容,此时它会退化为Vector,改用堆分配。
- 可靠性——二者都有长期在大型系统级商业应用上使用的经历,并且除了c++11版本特性引入外,代码实现上基本没有大的变动,所以可靠性均无问题。当然,为了保证效率,两者都不提供thread safe,最多提供reentrant
- 安全性——Qt变量存STL不存在安全隐患,毕竟都是class,只要是支持copy constructor和assignment operator的对象,都可以放心存STL。而且由于Qt对象广泛使用了写时复制机制,所以存储时时空开销非常小。
唯一的特例是 QWidget类型及其子类,这些类型 绝对不允许直接保存,只能用指针保存,哪怕用QTL也是这样。
但是, 不推荐用STL保存Qt对象,因为 代码风格不一致。不过QTL同时提供了Qt style API和STL style API,如果有和第三方库混合编程的需求,推荐用STL style API,这样可以随时替换类型。另外,QTL还提供了Java style iterator,对于一些习惯Java语法的用户会很方便。
对了,QTL比起STL,性能差别不明显,主要差异在
- QTL不支持allocator
- QTL没有shirnk接口
对于采用相同算法的容器,比如QVector和std::vector,各项操作的时间复杂度应该是相同的,差异只会在实现的语法细节。当然如果stl写了内存池用allocator的话,肯定会快上许多。
上图是Qt Document里Container Classes章节对各容器类性能的统计。从这里也可以看出,QList其实是个特殊的QVector,QSet其实是个特殊的QHash
============================
Benchmark算法设计
数据元素均为int,无论key还是value(懒得构造随机string了)
- List:一百万次push_back()随机值,一百万次push_front()随机值。成员查询都是靠遍历,就没必要专门测了
- ListWithInsert:相比上面的testcase,多了十万次insert操作,插入随机值到随机位置。感觉这个略蛋疼,没必要做
- Vector:一百万次push_back()随机值,一百万次随机查询并求和(求和结果没去用,只是怕不操作返回值的话,编译器直接把读操作优化没了)(QVector的insert是用下标索引,std::vector的insert是用迭代器索引,所以就不测insert了,太欺负人)
- Map:一百万次随机插入(key和value都是随机值),一百万次随机查询并求和
- Hash:同Map
- Set:同Map
- Bitset:初始化定长一百万,初值false。然后进行一百万次随机位置取反操作
- Array:初始化定长十万(一百万试了下,爆栈了),初值随机值。然后进行一百万次随机查询求和操作
测试平台
SurfaceBook I5 独显版
CPU:I5-6300U
内存:8GB
编译器:MinGW 5.3.0 32bit MSVC 2015 (14.0 amd64)
框架:Qt 5.7.0的Qt Test模块(提供代码段benchmark功能,若耗时过少会自动重复几次,统计平均值)
Benchmark结果
QTL与STL对比结论(在不使用allocator的前提下)
- QTL和STL性能,同样算法的容器基本在同一数量级
- Bitset容器可以看出,堆分配比栈分配有性能损失
- Set和Hash两者存在实现差异,所以benchmark结果差距较大。红黑树和hashtable的效率差距太大了……(后来得知STL有使用hashtable的std::unordered_set,不过没继续测了……)
- QVector的insert()是用index定位,std::vector()的insert是用iterator定位,明显不公平,所以vector的insert就没测了
- QList在拥有vector的操作性能的同时,通过前向扩展空间,模拟出了LinkedList的双向添加O(1)的特点。但存储int这种基础类型时,由于存在Node的构造开销,效率不如vector。但如果存储Struct的话,可能表现会好一点,待会儿我写个testcase试试看
- MSVC的Debug真凶残,不造加了多少overhead进去,这运行时间长的……
吐槽
- Qt的QQueue队列类,是直接继承自QList,只是添加了队列操作的接口。这难道不应该用LinkedList么,残念……如果我enqueue一百万次,dequeue一百万次,没有shrink功能的list,那内存开销……
- QStack栈类,继承自QVector,添加了栈操作接口。和QQueue一样残念……不过这个还算好了,内存应该不会爆掉,虽然我觉得,stack和queue这种无随机访问需求的,应该用LinkedList实现比较好……
- 出现个诡异的情况,Debug模式下,最后Array的testcase,莫名其妙的越界了,然后assert退出,囧……估计debug的栈分配和realse不同,所以爆栈了
源代码
// main.cpp
#include "test.h"
QTEST_MAIN(Test)
// test.h
#ifndef TEST_H
#define TEST_H
#include <QtCore>
#include <QtTest>
class Test : public QObject
{
Q_OBJECT
private slots:
void testQLinkedList();
void testStdList();
void testQList();
void testQLinkedListWithInsert();
void testStdListWithInsert();
void testQListWithInsert();
void testQVector();
void testStdVecor();
void testQMap();
void testStdMap();
void testQHash();
void testStdHash();
void testQSet();
void testStdSet();
void testQBitArray();
void testStdBitset();
void testQVarLengthArray();
void testStdArray();
};
#endif // TEST_H
// test.cpp
#include "Test.h"
#include <list>
#include <vector>
#include <map>
#include <unordered_map>
#include <bitset>
#include <set>
#include <array>
void Test::testQLinkedList()
{
QBENCHMARK {
QLinkedList<int> list;
for(int i=0;i<1000000;++i)
list.push_back(qrand());
for(int i=0;i<1000000;++i)
list.push_front(qrand());
}
}
void Test::testStdList()
{
QBENCHMARK {
std::list<int> list;
for(int i=0;i<1000000;++i)
list.push_back(qrand());
for(int i=0;i<1000000;++i)
list.push_front(qrand());
}
}
void Test::testQList()
{
QBENCHMARK {
QList<int> list;
for(int i=0;i<1000000;++i)
list.push_back(qrand());
for(int i=0;i<1000000;++i)
list.push_front(qrand());
}
}
void Test::testQLinkedListWithInsert()
{
QBENCHMARK {
QLinkedList<int> list;
for(int i=0;i<1000000;++i)
list.push_back(qrand());
for(int i=0;i<1000000;++i)
list.push_front(qrand());
for(int i=0;i<10000;++i)
list.insert(list.begin() + qrand() % list.size(), qrand());
}
}
void Test::testStdListWithInsert()
{
QBENCHMARK {
std::list<int> list;
for(int i=0;i<1000000;++i)
list.push_back(qrand());
for(int i=0;i<1000000;++i)
list.push_front(qrand());
for(int i=0;i<10000;++i)
{
auto it = list.begin();
int index = qrand() % list.size();
for(int j=0;j<index;++j)
++it;
list.insert(it, qrand());
}
}
}
void Test::testQListWithInsert()
{
QBENCHMARK {
QList<int> list;
for(int i=0;i<1000000;++i)
list.push_back(qrand());
for(int i=0;i<1000000;++i)
list.push_front(qrand());
for(int i=0;i<10000;++i)
{
auto it = list.begin();
int index = qrand() % list.size();
for(int j=0;j<index;++j)
++it;
list.insert(it, qrand());
}
}
}
void Test::testQVector()
{
QBENCHMARK {
QVector<int> vector;
for(int i=0;i<1000000;++i)
vector.push_back(qrand());
qint64 sum = 0;
for(int i=0;i<1000000;++i)
sum += vector.at(qrand() % vector.size());
}
}
void Test::testStdVecor()
{
QBENCHMARK {
std::vector<int> vector;
for(int i=0;i<1000000;++i)
vector.push_back(qrand());
qint64 sum = 0;
for(int i=0;i<1000000;++i)
sum += vector.at(qrand() % vector.size());
}
}
void Test::testQMap()
{
QBENCHMARK {
QMap<int, int> map;
for(int i=0;i<1000000;++i)
map.insert(qrand(), qrand());
qint64 sum = 0;
for(int i=0;i<1000000;++i)
sum += *map.find(qrand() % map.size());
}
}
void Test::testStdMap()
{
QBENCHMARK {
std::map<int, int> map;
for(int i=0;i<1000000;++i)
map.insert(std::make_pair(qrand(), qrand()));
qint64 sum = 0;
for(int i=0;i<1000000;++i)
sum += map.find(qrand() % map.size())->first;
}
}
void Test::testQHash()
{
QBENCHMARK {
QHash<int, int> hash;
for(int i=0;i<1000000;++i)
hash.insert(qrand(), qrand());
qint64 sum = 0;
for(int i=0;i<1000000;++i)
sum += *hash.find(qrand() % hash.size());
}
}
void Test::testStdHash()
{
QBENCHMARK {
std::unordered_map<int, int> hash;
for(int i=0;i<1000000;++i)
hash.insert(std::make_pair(qrand(), qrand()));
qint64 sum = 0;
for(int i=0;i<1000000;++i)
sum += hash.find(qrand() % hash.size())->second;
}
}
void Test::testQSet()
{
QBENCHMARK {
QSet<int> set;
for(int i=0;i<1000000;++i)
set.insert(qrand());
qint64 sum = 0;
for(int i=0;i<1000000;++i)
{
auto it = set.find(qrand());
if(it != set.cend())
sum += *it;
}
}
}
void Test::testStdSet()
{
QBENCHMARK {
std::set<int> set;
for(int i=0;i<1000000;++i)
set.insert(qrand());
qint64 sum = 0;
for(int i=0;i<1000000;++i)
{
auto it = set.find(qrand());
if(it != set.cend())
sum += *it;
}
}
}
void Test::testQBitArray()
{
QBENCHMARK {
QBitArray array(1000000, false);
for(int i=0;i<1000000;++i)
{
int index = qrand();
array[index] = !array[index];
}
}
}
void Test::testStdBitset()
{
QBENCHMARK {
std::bitset<1000000> array(false);
for(int i=0;i<1000000;++i)
{
int index = qrand();
array[index] = !array[index];
}
}
}
void Test::testQVarLengthArray()
{
QBENCHMARK {
QVarLengthArray<int, 100000> array;
for(int i=0;i<100000;++i)
array[i] = qrand();
qint64 sum = 0;
for(int i=0;i<1000000;++i)
sum += array[qrand() % array.size()];
}
}
void Test::testStdArray()
{
QBENCHMARK {
std::array<int, 100000> array;
for(int i=0;i<100000;++i)
array[i] = qrand();
qint64 sum = 0;
for(int i=0;i<1000000;++i)
sum += array[qrand() % array.size()];
}
}
===============================
QList、QVecotr、std::vector存储复杂类型测试
结果如图。QList性能略小于等于QVector,二者性能和std::vector无本质差别。 结果如图。QList性能略小于等于QVector,二者性能和std::vector无本质差别。
总结:QList在提供最大化的易用性的同时,带来了最小的性能损耗,若不使用prepend的话,可以换用QVecotr。
以下摘自QList帮助文档:
QList<T> is one of Qt's generic container classes. It stores items in a list that provides fast index-based access and index-based insertions and removals.翻译:QList<T>, QLinkedList<T>, and QVector<T> provide similar APIs and functionality. They are often interchangeable, but there are performance consequences. Here is an overview of use cases:
- QVector should be your default first choice. QVector<T> will usually give better performance than QList<T>, because QVector<T> always stores its items sequentially in memory, where QList<T> will allocate its items on the heap unless sizeof(T) <= sizeof(void*) and T has been declared to be either a Q_MOVABLE_TYPE or a Q_PRIMITIVE_TYPE using Q_DECLARE_TYPEINFO. See the Pros and Cons of Using QList for an explanation.
- However, QList is used throughout the Qt APIs for passing parameters and for returning values. Use QList to interface with those APIs.
- If you need a real linked list, which guarantees constant time insertions mid-list and uses iterators to items rather than indexes, use QLinkedList.
Note: QVector and QVarLengthArray both guarantee C-compatible array layout. QList does not. This might be important if your application must interface with a C API.
QList<T>是Qt的通用容器类之一。它用于列表存储元素,并提供快速的序号查询、序号插入和删除。
QList<T>、QLinkedList<T>、QVector<T>提供相似的API和功能。它们经常是可以互相替换的,但存在性能差异。下面是它们的用例介绍:
注意:QVector和QVarLengthArray都保证了对C数组的兼容。QList则不提供此兼容性。这在当你与C API交互时可能很重要。
- QVector应该是你的默认选择。QVecotr通常提供比QList更好的性能,因为QVector总是将所有元素在内存中顺序存储,而QList则是将各个元素分别分配到堆中,除非sizeof(T) <= sizeof(void*),并且T被通过Q_DECLARE_TYPEINFO()宏定义为Q_MOVABLE_TYPE(可以memcpy的类型)或Q_PRIMITIVE_TYPE(POD类型)。详见wordpress.com 的页面
- 然而,QList被Qt API广泛用于传递参数和返回值。在与这些API交互时使用QList。
- 如果你需要一个真正的链表,以保证常量级的插入,并且使用迭代器而非索引,那么选择QLinkedList。
Benchmark代码
struct PODData {
quint32 data = qrand();
};
Q_DECLARE_TYPEINFO(PODData, Q_PRIMITIVE_TYPE);
class PODClass {
public:
quint32 data = qrand();
PODClass() {}
PODClass(const PODClass& other) {data = other.data;}
PODClass(PODClass&& other) {data = other.data;}
PODClass& operator=(const PODClass& other)
{
if(this != &other)
data = other.data;
return *this;
}
PODClass& operator=(PODClass&& other)
{
data = other.data;
return *this;
}
};
Q_DECLARE_TYPEINFO(PODClass, Q_MOVABLE_TYPE);
class ComplexClass {
public:
int* data = nullptr;
ComplexClass() {data = new int;}
~ComplexClass() {clear();}
ComplexClass(const ComplexClass& other) {clear(); data = new int(*other.data);}
ComplexClass(ComplexClass&& other) {std::swap(this->data, other.data);}
ComplexClass& operator=(const ComplexClass& other)
{
if(this != &other)
{
clear();
data = new int(*other.data);
}
return *this;
}
ComplexClass& operator=(ComplexClass&& other)
{
std::swap(this->data, other.data);
return *this;
}
void clear() {if(data) delete data;}
};
Q_DECLARE_TYPEINFO(ComplexClass, Q_COMPLEX_TYPE);
void Test::testQListWithStruct()
{
QBENCHMARK {
QList<PODData> vector;
for(int i=0;i<100000;++i)
{
PODData data;
vector.push_back(data);
}
qint64 sum = 0;
for(int i=0;i<1000000;++i)
{
PODData data = vector.at(qrand() % vector.size());
sum += data.data;
}
}
}
void Test::testQVectorWithStruct()
{
QBENCHMARK {
QVector<PODData> vector;
for(int i=0;i<100000;++i)
{
PODData data;
vector.push_back(data);
}
qint64 sum = 0;
for(int i=0;i<1000000;++i)
{
PODData data = vector.at(qrand() % vector.size());
sum += data.data;
}
}
}
void Test::testStdVecorWithStruct()
{
QBENCHMARK {
std::vector<PODData> vector;
for(int i=0;i<100000;++i)
{
PODData data;
vector.push_back(data);
}
qint64 sum = 0;
for(int i=0;i<1000000;++i)
{
PODData data = vector.at(qrand() % vector.size());
sum += data.data;
}
}
}
void Test::testQListWithPODClass()
{
QBENCHMARK {
QList<PODClass> vector;
for(int i=0;i<100000;++i)
{
PODClass data;
vector.push_back(data);
}
qint64 sum = 0;
for(int i=0;i<1000000;++i)
{
PODClass data = vector.at(qrand() % vector.size());
sum += data.data;
}
}
}
void Test::testQVectorWithPODClass()
{
QBENCHMARK {
QVector<PODClass> vector;
for(int i=0;i<100000;++i)
{
PODClass data;
vector.push_back(data);
}
qint64 sum = 0;
for(int i=0;i<1000000;++i)
{
PODClass data = vector.at(qrand() % vector.size());
sum += data.data;
}
}
}
void Test::testStdVecorWithPODClass()
{
QBENCHMARK {
std::vector<PODClass> vector;
for(int i=0;i<100000;++i)
{
PODClass data;
vector.push_back(data);
}
qint64 sum = 0;
for(int i=0;i<1000000;++i)
{
PODClass data = vector.at(qrand() % vector.size());
sum += data.data;
}
}
}
void Test::testQListWithComplexClass()
{
QBENCHMARK {
QList<ComplexClass> vector;
for(int i=0;i<100000;++i)
{
ComplexClass data;
vector.push_back(data);
}
qint64 sum = 0;
for(int i=0;i<1000000;++i)
{
ComplexClass data = vector.at(qrand() % vector.size());
sum += *data.data;
}
}
}
void Test::testQVectorWithComplexClass()
{
QBENCHMARK {
QVector<ComplexClass> vector;
for(int i=0;i<100000;++i)
{
ComplexClass data;
vector.push_back(data);
}
qint64 sum = 0;
for(int i=0;i<1000000;++i)
{
ComplexClass data = vector.at(qrand() % vector.size());
sum += *data.data;
}
}
}
void Test::testStdVecorWithComplexClass()
{
QBENCHMARK {
std::vector<ComplexClass> vector;
for(int i=0;i<100000;++i)
{
ComplexClass data;
vector.push_back(data);
}
qint64 sum = 0;
for(int i=0;i<1000000;++i)
{
ComplexClass data = vector.at(qrand() % vector.size());
sum += *data.data;
}
}
}