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

Posted

技术标签:

【中文标题】QVector<int> 写入/调整大小性能【英文标题】:QVector<int> writing/resize performance 【发布时间】:2017-01-05 19:35:37 【问题描述】:

我试图将一些使用 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] = %d\n", 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] = %d\n", 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] = %d\n", 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] = %d\n", 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): %lf\n", time_spent);
    time_spent = measureExecutionTime(test2);
    printf("Test 2 (std::vector): %lf\n", time_spent);
    time_spent = measureExecutionTime(test3);
    printf("Test 3 (QVector clear): %lf\n", time_spent);
    time_spent = measureExecutionTime(test4);
    printf("Test 4 (QVector resize): %lf\n", 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。向量中的有效元素数量随着时间表的活动数量成功安排而不断变化 - 当给定活动无法放入时剩下的插槽,算法开始回滚,删除一些最后放置的活动,以便开始另一次调度尝试。

【问题讨论】:

结果是: -- 用于构建测试的编译器选项是......?换句话说,如果您正在为未优化的构建计时,那么您的发现毫无价值。 如果性能对时间至关重要,则不应在代码的时间敏感部分分配然后清除 QVector(或任何容器,实际上)。 如果你想创建一个全是2的向量,最快的方法大概是std::vector&lt;int&gt; vec(MAX_ELEMENTS, 2);。现在您将苹果与橙子进行比较。 如果您碰巧在使用 Visual Studio 并运行“调试”构建,那么所有这些都是有争议的。原因是 vector 由于调试版本中的迭代器检查而出名地慢。这就是为什么在询问性能时说明使用了哪些优化非常重要的原因。 @PaulMcKenzie,好的,对不起,我忘了这些信息很重要。帖子已编辑。 【参考方案1】:

其实正如MrEricSir在cmets部分指出的那样,clear()操作才是test4中真正的罪魁祸首。

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

void QVector::clear()

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

您可能正在使用 Qt 版本

【讨论】:

遗憾的是,它是 5.7.1(来自 Debian 测试包)【参考方案2】:

您的测试不是将苹果与苹果进行比较。

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;

性能应该相当相似。

【讨论】:

调整大小经常发生。我希望我编辑了原始帖子,添加了一些说明。我更改了程序以便从基准测试中删除 .reserve() 方法。结果没有改变。测试 1(普通 c):16.044388 测试 2(std::array):16.055172 测试 3(std::vector):91.533684 测试 4(QVector 清除):112.354447 测试 5(QVector 调整大小):41.223804 我还意识到 resize() 不会影响性能,因为我每次都使用相同的参数调用它(因此,它应该什么都不做)。我测试了它,它花了类似的时间。【参考方案3】:

测试开始时几乎是无稽之谈,不仅是无稽之谈,而且执行得很差。特别是在测试调整大小方面,使用静态大小的原始 C 数组。该测试如何在不进行任何调整大小的情况下调整大小?即使对于使用任何调整大小的容器,您也在调整到已经保留的值,这应该不会产生任何影响,因为您的 MAX_ELEMENTS 在整个测试过程中永远不会改变。

话虽如此,对于这种琐碎的操作QVector 很可能会成为一头奶牛。那是 Qt 对容器的隐式共享,使用 Copy-On-Write,这意味着执行的每个非常量方法都将检查容器数据是否正在共享,以便在这种情况下将其分离。该检查涉及原子,其中涉及同步,这是昂贵的。

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

  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,虽然我不确定具体原因,也许其他人可以详细说明。

【讨论】:

我不想单独测试矢量书写。我确实需要“调整”它。对不起,我自己没有说清楚。作为一个非英语母语的人,我避免写太多。阅读性能与我无关:详尽地完成的是写入 - 并更改数组/向量大小。 如果您对调整大小感兴趣,为什么要使用静态大小测试原始 C 数组?这是如何衡量任何事情的?在任何情况下调整大小都将涉及堆重新分配和数据复制,因此不同解决方案之间应该几乎相同。因此,就便利性和最佳写入速度而言,std::vector 似乎是正确的选择。 “调整大小”是指更改有效元素的数量。这就是为什么我对 Qt 和 std 向量使用 reserve() 方法,试图预先分配最大数量的元素。 Qt docs[1] 说 QVector::resize() 不会缩小向量容量。所以我认为重复这样做不会有问题。 [1]doc.qt.io/qt-5/qvector.html#resize 那么在这种情况下,您没有进行任何调整大小,您只是使用了不同大小的可用空间。

以上是关于QVector<int> 写入/调整大小性能的主要内容,如果未能解决你的问题,请参考以下文章

QVector<int>[index] 返回另一个 QVector?

无法访问复杂的 qvector 的方法

错误:“operator[]”不匹配(操作数类型为“QVector<int>”和“QCharRef”)

qvector倒序插入

QT迭代器 QVector<int>::iterator it;

qt5中QVector<double> 如何赋值