【Qt】深度解析QVector和QList


QVector和QList在实际开发中,如何选择这个问题,小生一直有这个疑问。无意间,在查阅资料时,发现了一篇分析得比较好的文章(文末有列出)。这篇文章的一些观点和数据也来源于该篇文章。


一、【QVector】解析

QVector可能是Qt中最接近STL的容器。尽管如此,QVector在许多平台上的性能还是比std::vector差,这是因为它的内部结构更复杂。通过比较GCC 4.3.2 (x86-64) -O2编译环境下,在QVector (Qt 4.6.3)以及std::vector上迭代操作部分生成的代码可知:
在这里插入图片描述

由以上汇编代码可知:

对于实际循环(. L11 /. L3)部分,QVector和std::vector是相同的,这表明编译器在这两种情况下都能很好识别迭代器。然而,区别在于在循环(.LCFI5/.LCFI2)之前填充%rbx (it)和%rbp (end)这部分代码:QVector的代码显然更复杂(包含了:更多的命令,更复杂的寻址模式,计算依赖于以前的结果),并且执行速度更慢。

这意味着对于空集合和小集合的迭代,QVector的开销将高于std::vector。QVector迭代生成的代码也比std::vector迭代稍微多一些。

QVector的一个优势是它的增长优化。当其元素类型为Q_MOVABLE_TYPE或Q_PRIMITIVE_TYPE时,QVector将使用realloc()来增加容量。STL容器也是这样做的,但是由于缺乏可移植的类型分类方案,它们只对针对于内置类型或不可移植的声明。

可以通过使用reserve()来消除此影响。这不仅是STL vector的一个优点,还可以加快Qt vector的速度并节省内存。

还应提到的是,由于内部函数QVector::realloc()的实现方式的影响,QVector要求元素类型提供一个默认构造函数,即使是简单的push_back()。相反,std::vector不要求元素类型为DefaultConstructible,除非调用显式要求的std::vector函数,例如std::vector(int)。


二、【QList】解析

QList是Qt容器的”白色坟墓”,“白色坟墓”,哈哈。

对于Qt来说,QList与std::list没有任何关系。QList被实现为一个数组列表,实际上是一个连续的void *s块,在前面和后面都有一点空间,允许前置(非常罕见)和追加(非常常见)操作。void *slot包含指向单个元素的指针(这些元素被复制构造到动态内存中,例如:使用new的时候),除非元素类型是可移动的,即声明为Q_MOVABLE_TYPE,并且足够小,即不大于void*,在这种情况下,元素被直接放入void*slot中。

下文将研究下这种设计的利弊:

从有利的方面来看:当针对生成的代码数量时,QList是一个真正的内存节省者。这是因为QList只是一个内部类的包装,内部类维护void*s的内存,这会使代码更紧凑,因为所有内存管理代码都是在不同类型的QLists之间共享的。

QList还允许较快的中间插入和相当有效的容器增长(只需要移动指针,而不是数据本身,所以QList总是可以使用realloc()来增长自己)操作。但是,这种效率不会比预期的QVector<void*>高多少,而QVector通常并不以中间快速插入而著称。

消极的一面是:当涉及到保存大多数数据类型时,QList存在真正的内存浪费现象。

这将会有两方面的影响:

(1)如果元素类型是可移动的并且足够小,那么当元素类型的大小小于sizeof(void*): sizeof(void*)-sizeof(T)时,QList会浪费内存。这是因为每个元素至少需要sizeof(void*)存储空间。换句话说:一个QList使用4×/8×(32/64位平台)的内存。

(2)如果元素类型不是可移动的或太大(sizeof(T) > sizeof(void*)),则将在堆上分配。因此,将为每个元素支付堆分配开销(取决于分配器,通常在0到12或2个字节之间),加上保存指针所需的4/8个字节

只有当元素类型是可移动且大小为sizeof(void*)时,QList才是一个好容器。这种情况至少发生在最重要的隐式共享Qt类型(QString, QByteArray,但也有QPen, QBrush等)中。下表描述的是Qt 4.6.3中隐式共享类型列表,以及它们是否适合作为QList的元素:

不适合QList:太大不适合QList:不是Moveable的适合
QBitmap, QFont, QGradient, QImage, QPalette, QPicture, QPixmap, QSqlField, QTextBoundaryFinder, QTextFormat, QVariant(!)QContiguousCache, QCursor, QDir, QFontInfo, QFontMetrics, QFontMetricsF, QGLColormap, QHash, QLinkedList, QList, QMap, QMultiHash, QMultiMap, QPainterPath, QPolygon, QPolygonF, QQueue, QRegion, QSet, QSqlQuery, QSqlRecord, QStack, QStringList, QTextCursor, QTextDocumentFragment, QVector, QX11InfoQBitArray, QBrush, QByteArray, QFileInfo, QIcon, QKeySequence, QLocale, QPen, QRegExp, QString, QUr

注:QCache缺少必要的复制构造函数,所以它不能作为QList中的元素使用。

在开发中,可以使用Q_DECLARE_TYPEINFO标记我们自己的类实例化:

Q_DECLARE_TYPEINFO( QList<MyType>, Q_MOVABLE_TYPE );

当将它们放入QList时,它们将是有效的。

然而,问题是当在放入QList时,如果忘记了使用Q_DECLARE_TYPEINFO标记该类型,那么就不能把他添加到QList中,至少不能以二进制兼容的方式添加它,因为声明一个可移动类型会改变该类型QList的内存布局。


下描述一些基本类型的内存效率。它们都是可移动的,因此作为QList元素的潜在效率很高。在表中,“大小”是元素类型的大小,“Mem / Elem”是每个元素使用的内存(以字节为单位),对于32/64-bit平台。

TypeSizeMem/ElemOverhead
bool/char14/8300%/700%
qint32/float44/80%/100%
qint64/double816+/8100%+/0%

特别注意的是会出现一个类型在32位平台上高效而在64位平台上低效(例如float)的情况,反之亦然(例如double)

【实践指南】当QList中T没有声明为Q_MOVABLE_TYPE或Q_PRIMITIVE_TYPE,或者sizeof(T) != sizeof(void*)(记住检查32和64位平台)时,避免使用QList。

三、总结

因此,QList不是一个好的默认容器。但是是否存在QList优于QVector的情况?遗憾的是,答案是否定的。这也是最好的答案,如果Qt 5只是去替换所有出现的QList与QVector。QList优于QVector的几个基准要么与在实践中无关,要么应该通过优化QVector来修复。

也就是说,在编写QVector之前,应该三思,即使它的性能可能会稍微好一些。这是因为QList通常用于Qt中QString的集合,在开发中,应该避免在Qt API使用QList的地方使用QVector。

四、尾

参考资料:
【1】https://marcmutz.wordpress.com/effective-qt/containers/#containers-qlist。
【2】https://doc.qt.io/qt-5/qlist.html。
【3】https://doc.qt.io/qt-5/qvector.html


搜索关注【嵌入式小生】wx公众号获取更多精彩内容>>>>
请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

iriczhao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值