QVector<int> 写入/调整大小性能

QVector<int> writing/resize performance

本文关键字:调整 性能 gt lt int QVector 写入      更新时间:2023-10-16

我试图将一些使用C风格整数向量的代码重构为QVector(其他代码使用Qt)
在此之前,我进行了性能测试,以检查此更改的糟糕程度。

我使用以下代码:

#include <QVector>
#include <vector>
#include <cstdio>
#include <ctime>
void test1(int MAX_ELEMENTS, int TIMES) {
int vec[MAX_ELEMENTS];
int nelems = 0;
for (int j=0; j<TIMES; j++) {
nelems = MAX_ELEMENTS;
for (int i=0; i<MAX_ELEMENTS; i++)
vec[i] = 2;
}
printf("Vec[0] = %dn", vec[0]);
}
void test2(int MAX_ELEMENTS, int TIMES) {
std::vector<int> vec;
vec.reserve(MAX_ELEMENTS);
for (int j=0; j<TIMES; j++) {
vec.clear();
for (int i=0; i<MAX_ELEMENTS; i++)
vec.push_back(2);
}
printf("Vec[0] = %dn", vec[0]);
}
void test3(int MAX_ELEMENTS, int TIMES) {
QVector<int> vec;
vec.reserve(MAX_ELEMENTS);
for (int j=0; j<TIMES; j++) {
vec.clear();
for (int i=0; i<MAX_ELEMENTS; i++)
vec.push_back(2);
}
printf("Vec[0] = %dn", vec[0]);
}
void test4(int MAX_ELEMENTS, int TIMES) {
QVector<int> vec;
vec.reserve(MAX_ELEMENTS);
for (int j=0; j<TIMES; j++) {
vec.resize(MAX_ELEMENTS);
for (int i=0; i<MAX_ELEMENTS; i++)
vec[i] = 2;
}
printf("Vec[0] = %dn", vec[0]);
}
double measureExecutionTime(void (*func)(int, int)) {
const int MAX_ELEMENTS=30000;
const int TIMES=2000000;
clock_t begin, end;
begin = clock();
(*func)(MAX_ELEMENTS, TIMES);
end = clock();
return (double)(end - begin) / CLOCKS_PER_SEC;
}
int main() {
double time_spent;
time_spent = measureExecutionTime(test1);
printf("Test 1 (plain c): %lfn", time_spent);
time_spent = measureExecutionTime(test2);
printf("Test 2 (std::vector): %lfn", time_spent);
time_spent = measureExecutionTime(test3);
printf("Test 3 (QVector clear): %lfn", time_spent);
time_spent = measureExecutionTime(test4);
printf("Test 4 (QVector resize): %lfn", time_spent);
return 0;
}

结果是:

Vec[0] = 2
Test 1 (plain c): 16.130129
Vec[0] = 2
Test 2 (std::vector): 92.719583
Vec[0] = 2
Test 3 (QVector clear): 109.882463
Vec[0] = 2
Test 4 (QVector resize): 46.261172

为了提高QVector的性能,有什么不同的想法吗?该向量每秒从0填充几次到其新大小(用于时间表调度软件)。


Qt版本:5.7.1(+dsfg1,来自Debian测试)。

我用来从Linux shell编译的命令行:

g++ -c -m64 -pipe -O2 -Wall -W -D_REENTRANT -fPIC -DQT_NO_DEBUG -DQT_GUI_LIB -DQT_CORE_LIB -I. -I. -isystem /usr/include/x86_64-linux-gnu/qt5 -isystem /usr/include/x86_64-linux-gnu/qt5/QtGui -isystem /usr/include/x86_64-linux-gnu/qt5/QtCore -I. -I/usr/lib/x86_64-linux-gnu/qt5/mkspecs/linux-g++-64 -o teste.o teste.cpp

需要明确的是:向量元素不相等,我只是把它们等于2。向量中有效元素的数量随着时间表的活动数量的成功调度而不断变化-当给定的活动无法放置在剩余的插槽中时,算法开始回滚,删除一些最后放置的活动,以便开始另一次调度尝试。

实际上,正如EricSir先生在评论部分指出的那样,clear()操作是test4的真正罪魁祸首。

如果你查看Qt文档,你会发现以下内容:

void QVector::clear()

从矢量中删除所有元素。
注意:直到Qt 5.6,这也释放了向量使用的内存。从问题5.7开始,容量得以保留。

您可能正在使用Qt版本<5.7,其强制释放循环内的存储器。

您的测试不是在比较苹果。

  • int vec[MAX_ELEMENTS]将在堆栈上分配,而std::vectorQVector将使用堆。基于堆栈的分配要快得多。如果您可以摆脱基于堆栈的分配,请考虑std::array
  • 如果您只是对用值填充向量感兴趣,请尝试仅对这一部分进行基准测试

换句话说,尝试基准测试:

for(int i = 0; i < max_size; ++i)
c_array[i] = i;

std_vector.resize(max_size); // <- initialization, not benchmarked
for(int i = 0; i < max_size; ++i)
std_vector[i] = i;

qvector.resize(max_size); // <- initialization, not benchmarked
for(int i = 0; i < max_size; ++i)
qvector[i] = i;

性能应该相当相似。

这个测试一开始几乎是胡说八道,不仅是胡说八道,而且实现得很差。特别是在测试大小调整方面,使用静态大小的原始C数组。如何在不进行任何调整的情况下测试调整大小?即使对于使用任何大小调整的容器,也要将大小调整为已经保留的值,这不应该有任何影响,因为MAX_ELEMENTS在整个测试过程中从未变化。

也就是说,对于这种琐碎的操作,QVector很可能会受到COW的影响。这就是Qt对容器的隐式共享,即写时复制,这意味着执行的每个非常量方法都将检查容器数据是否共享,以便在这种情况下分离它。这种检查涉及到原子,这涉及到同步,这是昂贵的。

如果您要对写访问执行更充分的测试,例如:

int elements = 30000, times = 200000;
QElapsedTimer t;
int * ia = new int[elements];
t.start();
for (int it = 0; it < times; ++ it) {
for (int ic = 0; ic < elements; ++ic) {
ia[ic] = 2;
}
}
qDebug() << t.elapsed() << " msec for raw array";    
QVector<int> iqv(elements);
t.restart();
for (int it = 0; it < times; ++ it) {
for (int ic = 0; ic < elements; ++ic) {
iqv[ic] = 2;
}
}
qDebug() << t.elapsed() << " msec for qvector";
std::vector<int> isv;
isv.reserve(elements);
t.restart();
for (int it = 0; it < times; ++ it) {
for (int ic = 0; ic < elements; ++ic) {
isv[ic] = 2;
}
}
qDebug() << t.elapsed() << " msec for std::vector";

你会看到与上面所说的非常有共鸣的结果,在我的系统上,结果是:

1491  msec for raw array
4238  msec for qvector
1491  msec for std::vector

原始/C阵列和std::vector的时间实际上是相同的,而QVector则处于衰退状态。

如果我们要通过累积容器值来测试读取,情况正好相反,请注意,在这种情况下,我们将at(index) const用于QVector:

2169  msec for raw array
2170  msec for qvector
2801  msec for std::vector

在没有COW惩罚的情况下,QVector具有与原始C阵列相同的性能。是std::vector在这里输了,虽然我不确定确切的原因,也许其他人可以详细说明。