【问题标题】:Strange (huge) performance difference between std::vector, QList and std::liststd::vector、QList 和 std::list 之间奇怪的(巨大的)性能差异
【发布时间】:2014-03-16 00:00:33
【问题描述】:

在编写一个处理大量元素(约 100k)的程序时,我注意到 std::listQList 之间有一个奇怪的区别。 一开始我使用了一个std::vector,它表现不错。但是因为程序经常需要在向量中的随机位置插入元素,所以我切换到 std::list,当迭代器位于所需位置时,它的插入时间是恒定的。

问题是,std::list 在 insert() 和 push_back() 方法中的表现都比 std::vector 差。 测量将 100 个连续元素添加到我得到的 100k 个元素的列表中:

  • ~25ms 用于 std::vector(最坏的情况是在开头添加)
  • std::list 超过 600 毫秒(!)(任何位置,即使使用 push_back() 或 insert(list.begin())。

请注意,插入元素的时间不包括迭代器到达位置的时间。

我知道列表的性能问题是由于列表导致缓存未命中,但这似乎超出了缓存未命中的限制。 此外,插入元素(具有 5 个恒定长度变量的简单结构)的时间随着列表的大小而增加。即使是获取列表大小的操作也需要更多时间。 这与列表的两个操作所保证的时间复杂度完全相反:constant

见:Here

出于好奇,从 std::list 更改为 QList 和 viola:插入时间是恒定的,介于 0ms 和 1ms 之间。

这是用于测量插入时间的代码。

两个时间点之间没有执行其他操作:错误:使用了 size() 方法

std::列表:

    QTime time;
    time.start();

    for (int a = 1; a <= lineChange; a++)
    {
        listData.insert(listIterator, newElement); 
    }        
    int elapsed = time.restart();
    qDebug() << "elapsed: " << elapsed << "ms";

结果:经过:662ms

QList:

    QTime time;
    time.start();

    for (int a = 1; a <= lineChange; a++)
    {
        QListData.insert(iteratorPos, newElement);
        position++;
    } 

    int elapsed = time.restart();
    qDebug() << "elapsed: " << elapsed << "ms";

结果:经过:1ms

std::vector:

    QTime time;
    time.start();

    for (int a = 1; a <= lineChange; a++)
    {
        vectorData.insert(vectorIterator, newElement);

        /*update of the iterator when it was 
        invalidated due to resize of the vector*/
    }    

    int elapsed = time.restart();
    qDebug() << "elapsed: " << elapsed << "ms";

结果:经过:27ms

那么,为什么 QList 和 std::list 之间存在如此巨大的差异呢? 或者更好:为什么 std::list 的性能如此糟糕?

作为附带信息:我在 Linux (Mint) 下使用带有 gcc 的 QtEditor,并将标志设置为 c++11

编辑:

数据类型和声明:

typedef struct TOKELEMENT {
    unsigned int column;
    unsigned int lenght;
    unsigned int tType;
    std::string value;
} tokElement;

// the three lists
std::vector<okElement> vectorData;
std::list<tokElement> listData;
QList<tokElement> QListData;

tokElement newElement;

unsigned int iteratorPos;
std::vector<std::vector<tokElement> >::iterator vectorIterator;
std::list<std::vector<tokElement> >::iterator listIterator;

//lineChange is an unsigned int, given as function parameter
unsigned int lineChange;

【问题讨论】:

  • 请提供SSCCE
  • 没有上下文 (SSCCE),我们只能猜测发生了什么。例如,我可以猜测QList 可能只存储一个指针(取决于元素的类型),而listvector 按值存储。此外,我可以猜测list 没有保留“空闲列表”,因此必须为每个插入的元素分配。
  • 您能否展示足够多的代码以查看所有相关变量的声明,尤其是ElementListactIteratorlineChange
  • 另外,请注意 QList 不是链表。来自docs:“在内部,QList 表示为指向 T 类型项的指针数组。如果 T 本身是指针类型或不大于指针的基本类型,或者 T 是Qt 的共享类,然后 QList 将项目直接存储在指针数组中。”
  • 您提到 size() 表现不佳 - 这可能是由于 gcc 在这种情况下没有维护新标准。见 - stackoverflow.com/questions/19154205/…

标签: performance c++11 stdvector qlist stdlist


【解决方案1】:

与我在问题中提到的相反(我感到羞耻),我在 for-loop 中再次检查了 std::list 的大小确定是否在列表中使用 insert() 或 push_back()。 由于这个函数没有 O(1) 但 O(n) 的时间复杂度,这大大减慢了整个插入的速度。感谢 Leeor 指出这一点。

在将该检查移出 for-loop 后,std::list 的执行与预期一样,甚至比 QList 更快。

【讨论】:

  • 请注意,编写良好的std::vector 版本将使用质量插入算法来添加元素(可能在遍历时),而不是循环。为了使列表“公平”获胜,您必须 find 插入位置数量级低于您在该位置 insert 的频率,并且您需要使用插入之间的容器(同时希望几乎总是保持插入位置)。
猜你喜欢
  • 2015-05-19
  • 1970-01-01
  • 1970-01-01
  • 2014-05-23
  • 1970-01-01
  • 2010-09-19
  • 2018-06-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多